From 0fd347eeba07ae61e19c6d5a2fef074e80c2f513 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 6 Mar 2024 17:02:16 -0800 Subject: [PATCH 0001/1047] Rename registration.json and add ProvideSettingsManifest --- src/VisualStudio/CSharp/Impl/CSharpPackage.cs | 2 +- .../Setup/Roslyn.VisualStudio.Setup.csproj | 1 + .../Setup/csharpSettings.registration.json | 251 ++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 src/VisualStudio/Setup/csharpSettings.registration.json diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index ccae4e77195f6..ff8ea36302568 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService [ProvideLanguageEditorOptionPage(typeof(Options.Formatting.FormattingSpacingPage), "CSharp", @"Code Style\Formatting", "Spacing", pageNameResourceId: "#112", keywordListResourceId: 310)] [ProvideLanguageEditorOptionPage(typeof(Options.NamingStylesOptionPage), "CSharp", @"Code Style", "Naming", pageNameResourceId: "#115", keywordListResourceId: 314)] [ProvideLanguageEditorOptionPage(typeof(Options.IntelliSenseOptionPage), "CSharp", null, "IntelliSense", pageNameResourceId: "#103", keywordListResourceId: 312)] - [ProvideSettingsManifest(PackageRelativeManifestFile = @"UnifiedSettings\csharpSettings.registration.json")] + [ProvideSettingsManifest] [Guid(Guids.CSharpPackageIdString)] internal sealed class CSharpPackage : AbstractPackage, IVsUserSettingsQuery { diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index ee3c8f5f5dc30..5237ce0d14b1f 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -347,6 +347,7 @@ true false + diff --git a/src/VisualStudio/Setup/csharpSettings.registration.json b/src/VisualStudio/Setup/csharpSettings.registration.json new file mode 100644 index 0000000000000..a5a62ad827f35 --- /dev/null +++ b/src/VisualStudio/Setup/csharpSettings.registration.json @@ -0,0 +1,251 @@ +// NOTE: +// When this file is changed. Please also update the cache tag under settings entry in src/VisualStudio/CSharp/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.csharp.intellisense.triggerCompletionOnTypingLetters": { + "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 0, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.TriggerOnTypingLetters" + } + } + } + }, + // CompletionOptionsStorage.TriggerOnDeletion + "textEditor.csharp.intellisense.triggerCompletionOnDeletion": { + "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": false, + "order": 1, + "enableWhen": "${config:textEditor.csharp.intellisense.triggerCompletionOnTypingLetters}=='true'", + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.TriggerOnDeletion" + } + } + } + }, + // CompletionOptionsStorage.TriggerInArgumentLists + "textEditor.csharp.intellisense.triggerCompletionInArgumentLists": { + "title": "@Automatically_show_completion_list_in_argument_lists;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 10, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.TriggerInArgumentLists" + } + } + } + }, + // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems + "textEditor.csharp.intellisense.highlightMatchingPortionsOfCompletionListItems": { + "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 20, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.HighlightMatchingPortionsOfCompletionListItems" + } + } + } + }, + // CompletionViewOptionsStorage.ShowCompletionItemFilters + "textEditor.csharp.intellisense.showCompletionItemFilters": { + "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 30, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowCompletionItemFilters" + } + } + } + }, + // CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon + "textEditor.csharp.intellisense.completeStatementOnSemicolon": { + "title": "@Automatically_complete_statement_on_semicolon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 40, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.AutomaticallyCompleteStatementOnSemicolon" + } + } + } + }, + // CompletionOptionsStorage.SnippetsBehavior + "textEditor.csharp.intellisense.snippetsBehavior": { + "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "string", + "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], + "enumItemLabels": [ "@Never_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], + "default": "alwaysInclude", + "order": 50, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.SnippetsBehavior" + }, + "map": [ + { + "result": "neverInclude", + "match": 1 + }, + // '0' matches to SnippetsRule.Default. Means the behavior is decided by langauge. + // '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 unifed settings would persist '2' to storage when 'alwasyInclude' is selected. + { + "result": "alwaysInclude", + "match": 2 + }, + { + "result": "alwaysInclude", + "match": 0 + }, + { + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 3 + } + ] + } + } + }, + // CompletionOptionsStorage.EnterKeyBehavior + "textEditor.csharp.intellisense.returnKeyCompletionBehavior": { + "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "string", + "enum": [ "never", "afterFullyTypedWord", "always" ], + "enumItemLabels": [ "@Never_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], + "default": "never", + "order": 60, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.EnterKeyBehavior" + }, + "map": [ + // '0' matches to EnterKeyRule.Default. Means the behavior is decided by langauge. + // '1' matches to SnippetsRule.Never. 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 '1' in front, so unifed settings would persist '2' to storage when 'never' is selected. + { + "result": "never", + "match": 1 + }, + { + "result": "never", + "match": 0 + }, + { + "result": "always", + "match": 2 + }, + { + "result": "afterFullyTypedWord", + "match": 3 + } + ] + } + } + }, + // CompletionOptionsStorage.ShowNameSuggestions + "textEditor.csharp.intellisense.showNameCompletionSuggestions": { + "title": "@Show_name_suggestions;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 70, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowNameSuggestions" + } + } + } + }, + // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces + "textEditor.csharp.intellisense.showCompletionItemsFromUnimportedNamespaces": { + "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 80, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowItemsFromUnimportedNamespaces" + } + } + } + }, + // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets + "textEditor.csharp.intellisense.enableArgumentCompletionSnippets": { + "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", + "type": "boolean", + "default": false, + "order": 90, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.EnableArgumentCompletionSnippets" + } + } + } + }, + // CompletionOptionsStorage.ShowNewSnippetExperienceUserOption + "textEditor.csharp.intellisense.showNewSnippetExperience": { + "title": "@Show_new_snippet_experience_experimental;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": false, + "alternateDefault": { + // CompletionOptionsStorage.ShowNewSnippetExperienceFeatureFlag + "flagName": "Roslyn.SnippetCompletion", + "default": true + }, + "order": 100, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowNewSnippetExperience" + } + } + } + } + }, + "categories": { + "textEditor.csharp":{ + "title": "C#" + }, + "textEditor.csharp.intellisense": { + "title": "@103;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", + "legacyOptionPageId": "EDE66829-7A36-4c5d-8E20-9290195DCF80" + } + } +} From 9f9c6daf4dcaa86473a076025862dca20896c37f Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 6 Mar 2024 17:19:17 -0800 Subject: [PATCH 0002/1047] Add name --- src/VisualStudio/CSharp/Impl/CSharpPackage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index ff8ea36302568..40303c27c8562 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService [ProvideLanguageEditorOptionPage(typeof(Options.Formatting.FormattingSpacingPage), "CSharp", @"Code Style\Formatting", "Spacing", pageNameResourceId: "#112", keywordListResourceId: 310)] [ProvideLanguageEditorOptionPage(typeof(Options.NamingStylesOptionPage), "CSharp", @"Code Style", "Naming", pageNameResourceId: "#115", keywordListResourceId: 314)] [ProvideLanguageEditorOptionPage(typeof(Options.IntelliSenseOptionPage), "CSharp", null, "IntelliSense", pageNameResourceId: "#103", keywordListResourceId: 312)] - [ProvideSettingsManifest] + [ProvideSettingsManifest(PackageRelativeManifestFile = (@".\csharpSettings.registration.json"))] [Guid(Guids.CSharpPackageIdString)] internal sealed class CSharpPackage : AbstractPackage, IVsUserSettingsQuery { From 566ca4e17c572ef063c54d9b7d3356742d4e5fb5 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 7 Mar 2024 17:42:40 -0800 Subject: [PATCH 0003/1047] Fix name --- src/VisualStudio/CSharp/Impl/CSharpPackage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index 40303c27c8562..956d250ada64c 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService [ProvideLanguageEditorOptionPage(typeof(Options.Formatting.FormattingSpacingPage), "CSharp", @"Code Style\Formatting", "Spacing", pageNameResourceId: "#112", keywordListResourceId: 310)] [ProvideLanguageEditorOptionPage(typeof(Options.NamingStylesOptionPage), "CSharp", @"Code Style", "Naming", pageNameResourceId: "#115", keywordListResourceId: 314)] [ProvideLanguageEditorOptionPage(typeof(Options.IntelliSenseOptionPage), "CSharp", null, "IntelliSense", pageNameResourceId: "#103", keywordListResourceId: 312)] - [ProvideSettingsManifest(PackageRelativeManifestFile = (@".\csharpSettings.registration.json"))] + [ProvideSettingsManifest(PackageRelativeManifestFile = (@"csharpSettings.registration.json"))] [Guid(Guids.CSharpPackageIdString)] internal sealed class CSharpPackage : AbstractPackage, IVsUserSettingsQuery { From 8f5ce764b7393cdc0a733347d7d9f7a8a7f3121d Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 7 Mar 2024 17:43:02 -0800 Subject: [PATCH 0004/1047] Clean --- src/VisualStudio/CSharp/Impl/CSharpPackage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index 956d250ada64c..06b6755146c1f 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService [ProvideLanguageEditorOptionPage(typeof(Options.Formatting.FormattingSpacingPage), "CSharp", @"Code Style\Formatting", "Spacing", pageNameResourceId: "#112", keywordListResourceId: 310)] [ProvideLanguageEditorOptionPage(typeof(Options.NamingStylesOptionPage), "CSharp", @"Code Style", "Naming", pageNameResourceId: "#115", keywordListResourceId: 314)] [ProvideLanguageEditorOptionPage(typeof(Options.IntelliSenseOptionPage), "CSharp", null, "IntelliSense", pageNameResourceId: "#103", keywordListResourceId: 312)] - [ProvideSettingsManifest(PackageRelativeManifestFile = (@"csharpSettings.registration.json"))] + [ProvideSettingsManifest(PackageRelativeManifestFile = "csharpSettings.registration.json")] [Guid(Guids.CSharpPackageIdString)] internal sealed class CSharpPackage : AbstractPackage, IVsUserSettingsQuery { From e94de2f2553f377aaccb32b25118e99e7803d997 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 12:22:37 -0800 Subject: [PATCH 0005/1047] Add an entry in pkgdef --- src/VisualStudio/CSharp/Impl/CSharpPackage.cs | 2 +- ...isualStudio.LanguageServices.CSharp.csproj | 5 + .../Setup/Roslyn.VisualStudio.Setup.csproj | 1 - .../Setup/csharpSettings.registration.json | 251 ------------------ 4 files changed, 6 insertions(+), 253 deletions(-) delete mode 100644 src/VisualStudio/Setup/csharpSettings.registration.json diff --git a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs index 06b6755146c1f..ccae4e77195f6 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpPackage.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpPackage.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService [ProvideLanguageEditorOptionPage(typeof(Options.Formatting.FormattingSpacingPage), "CSharp", @"Code Style\Formatting", "Spacing", pageNameResourceId: "#112", keywordListResourceId: 310)] [ProvideLanguageEditorOptionPage(typeof(Options.NamingStylesOptionPage), "CSharp", @"Code Style", "Naming", pageNameResourceId: "#115", keywordListResourceId: 314)] [ProvideLanguageEditorOptionPage(typeof(Options.IntelliSenseOptionPage), "CSharp", null, "IntelliSense", pageNameResourceId: "#103", keywordListResourceId: 312)] - [ProvideSettingsManifest(PackageRelativeManifestFile = "csharpSettings.registration.json")] + [ProvideSettingsManifest(PackageRelativeManifestFile = @"UnifiedSettings\csharpSettings.registration.json")] [Guid(Guids.CSharpPackageIdString)] internal sealed class CSharpPackage : AbstractPackage, IVsUserSettingsQuery { diff --git a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj index 6ff52d800af00..e7fd4b4508d33 100644 --- a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj +++ b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj @@ -16,6 +16,11 @@ false true + + + true + + diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 5237ce0d14b1f..ee3c8f5f5dc30 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -347,7 +347,6 @@ true false - diff --git a/src/VisualStudio/Setup/csharpSettings.registration.json b/src/VisualStudio/Setup/csharpSettings.registration.json deleted file mode 100644 index a5a62ad827f35..0000000000000 --- a/src/VisualStudio/Setup/csharpSettings.registration.json +++ /dev/null @@ -1,251 +0,0 @@ -// NOTE: -// When this file is changed. Please also update the cache tag under settings entry in src/VisualStudio/CSharp/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.csharp.intellisense.triggerCompletionOnTypingLetters": { - "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 0, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.TriggerOnTypingLetters" - } - } - } - }, - // CompletionOptionsStorage.TriggerOnDeletion - "textEditor.csharp.intellisense.triggerCompletionOnDeletion": { - "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": false, - "order": 1, - "enableWhen": "${config:textEditor.csharp.intellisense.triggerCompletionOnTypingLetters}=='true'", - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.TriggerOnDeletion" - } - } - } - }, - // CompletionOptionsStorage.TriggerInArgumentLists - "textEditor.csharp.intellisense.triggerCompletionInArgumentLists": { - "title": "@Automatically_show_completion_list_in_argument_lists;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 10, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.TriggerInArgumentLists" - } - } - } - }, - // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems - "textEditor.csharp.intellisense.highlightMatchingPortionsOfCompletionListItems": { - "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 20, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.HighlightMatchingPortionsOfCompletionListItems" - } - } - } - }, - // CompletionViewOptionsStorage.ShowCompletionItemFilters - "textEditor.csharp.intellisense.showCompletionItemFilters": { - "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 30, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowCompletionItemFilters" - } - } - } - }, - // CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon - "textEditor.csharp.intellisense.completeStatementOnSemicolon": { - "title": "@Automatically_complete_statement_on_semicolon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 40, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.AutomaticallyCompleteStatementOnSemicolon" - } - } - } - }, - // CompletionOptionsStorage.SnippetsBehavior - "textEditor.csharp.intellisense.snippetsBehavior": { - "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "string", - "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], - "enumItemLabels": [ "@Never_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], - "default": "alwaysInclude", - "order": 50, - "migration": { - "enumIntegerToString": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.SnippetsBehavior" - }, - "map": [ - { - "result": "neverInclude", - "match": 1 - }, - // '0' matches to SnippetsRule.Default. Means the behavior is decided by langauge. - // '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 unifed settings would persist '2' to storage when 'alwasyInclude' is selected. - { - "result": "alwaysInclude", - "match": 2 - }, - { - "result": "alwaysInclude", - "match": 0 - }, - { - "result": "includeAfterTypingIdentifierQuestionTab", - "match": 3 - } - ] - } - } - }, - // CompletionOptionsStorage.EnterKeyBehavior - "textEditor.csharp.intellisense.returnKeyCompletionBehavior": { - "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "string", - "enum": [ "never", "afterFullyTypedWord", "always" ], - "enumItemLabels": [ "@Never_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], - "default": "never", - "order": 60, - "migration": { - "enumIntegerToString": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.EnterKeyBehavior" - }, - "map": [ - // '0' matches to EnterKeyRule.Default. Means the behavior is decided by langauge. - // '1' matches to SnippetsRule.Never. 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 '1' in front, so unifed settings would persist '2' to storage when 'never' is selected. - { - "result": "never", - "match": 1 - }, - { - "result": "never", - "match": 0 - }, - { - "result": "always", - "match": 2 - }, - { - "result": "afterFullyTypedWord", - "match": 3 - } - ] - } - } - }, - // CompletionOptionsStorage.ShowNameSuggestions - "textEditor.csharp.intellisense.showNameCompletionSuggestions": { - "title": "@Show_name_suggestions;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 70, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowNameSuggestions" - } - } - } - }, - // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces - "textEditor.csharp.intellisense.showCompletionItemsFromUnimportedNamespaces": { - "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 80, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowItemsFromUnimportedNamespaces" - } - } - } - }, - // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets - "textEditor.csharp.intellisense.enableArgumentCompletionSnippets": { - "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", - "type": "boolean", - "default": false, - "order": 90, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.EnableArgumentCompletionSnippets" - } - } - } - }, - // CompletionOptionsStorage.ShowNewSnippetExperienceUserOption - "textEditor.csharp.intellisense.showNewSnippetExperience": { - "title": "@Show_new_snippet_experience_experimental;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": false, - "alternateDefault": { - // CompletionOptionsStorage.ShowNewSnippetExperienceFeatureFlag - "flagName": "Roslyn.SnippetCompletion", - "default": true - }, - "order": 100, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowNewSnippetExperience" - } - } - } - } - }, - "categories": { - "textEditor.csharp":{ - "title": "C#" - }, - "textEditor.csharp.intellisense": { - "title": "@103;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", - "legacyOptionPageId": "EDE66829-7A36-4c5d-8E20-9290195DCF80" - } - } -} From 2e700ac49ef2f4ccfa470bcaeababfc79f937d3d Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 12:26:38 -0800 Subject: [PATCH 0006/1047] Add comment --- src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index ee45c4813cee4..49a5494cefb78 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -193,7 +193,7 @@ "SortPriority"=dword:00000064 @="Microsoft Visual C#" -// CacheTag value should be changed when registration file changes +// CacheTag value should be changed everytime when the registration file get changed // 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\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] @="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" From 219a1023b0a54f01d70a6fd9cbf7b58336766682 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 12:33:13 -0800 Subject: [PATCH 0007/1047] Change csproj --- .../Microsoft.VisualStudio.LanguageServices.CSharp.csproj | 5 ----- src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj index e7fd4b4508d33..6ff52d800af00 100644 --- a/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj +++ b/src/VisualStudio/CSharp/Impl/Microsoft.VisualStudio.LanguageServices.CSharp.csproj @@ -16,11 +16,6 @@ false true - - - true - - diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index 49a5494cefb78..ee45c4813cee4 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -193,7 +193,7 @@ "SortPriority"=dword:00000064 @="Microsoft Visual C#" -// CacheTag value should be changed everytime when the registration file get changed +// 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\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] @="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" From e45f5060204cac6178bf136b22c26b511f5f00f1 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 11 Mar 2024 11:00:21 -0700 Subject: [PATCH 0008/1047] Fix source build --- eng/SourceBuildPrebuiltBaseline.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 592d3954059e8..7caa4e4c36196 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -41,5 +41,6 @@ + From 990fa52ecf306ae039a9499badc09fd0446c4091 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 14:11:36 -0800 Subject: [PATCH 0009/1047] Add entries for VB settings --- .../CSharp/Impl/PackageRegistration.pkgdef | 6 +- .../LanguageService/VisualBasicPackage.vb | 1 + ...Studio.LanguageServices.VisualBasic.vbproj | 8 +- .../Impl/PackageRegistration.pkgdef | 9 +- .../visualBasicSettings.registration.json | 247 ++++++++++++++++++ 5 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index ee45c4813cee4..79d2739217e87 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -195,7 +195,7 @@ // 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\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] -@="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" -"ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" +[$RootKey$\SettingsManifests\{574fc912-f74f-4b4e-92c3-f695c208a2bb}] +@="Microsoft.VisualStudio.LanguageServices.VisualBasic.VisualBasicPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\visualBasicSettings.registration.json" "CacheTag"=qword:08DC1824DFE0117B 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..d12fd4d5c7671 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,7 +50,7 @@ - + diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index bfb3a04c6856c..8179de4f741c4 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\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] +@="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" +"CacheTag"=qword:08DC1824DFE0117B 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..2253fef5a7a06 --- /dev/null +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -0,0 +1,247 @@ +{ + "properties": { + // CompletionOptionsStorage.TriggerOnTypingLetters + "textEditor.csharp.intellisense.triggerCompletionOnTypingLetters": { + "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 0, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.TriggerOnTypingLetters" + } + } + } + }, + // CompletionOptionsStorage.TriggerOnDeletion + "textEditor.csharp.intellisense.triggerCompletionOnDeletion": { + "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": false, + "order": 1, + "enableWhen": "${config:textEditor.csharp.intellisense.triggerCompletionOnTypingLetters}=='true'", + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.TriggerOnDeletion" + } + } + } + }, + // CompletionOptionsStorage.TriggerInArgumentLists + "textEditor.csharp.intellisense.triggerCompletionInArgumentLists": { + "title": "@Automatically_show_completion_list_in_argument_lists;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 10, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.TriggerInArgumentLists" + } + } + } + }, + // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems + "textEditor.csharp.intellisense.highlightMatchingPortionsOfCompletionListItems": { + "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 20, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.HighlightMatchingPortionsOfCompletionListItems" + } + } + } + }, + // CompletionViewOptionsStorage.ShowCompletionItemFilters + "textEditor.csharp.intellisense.showCompletionItemFilters": { + "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 30, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowCompletionItemFilters" + } + } + } + }, + // CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon + "textEditor.csharp.intellisense.completeStatementOnSemicolon": { + "title": "@Automatically_complete_statement_on_semicolon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 40, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.AutomaticallyCompleteStatementOnSemicolon" + } + } + } + }, + // CompletionOptionsStorage.SnippetsBehavior + "textEditor.csharp.intellisense.snippetsBehavior": { + "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "string", + "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], + "enumItemLabels": [ "@Never_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], + "default": "alwaysInclude", + "order": 50, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.SnippetsBehavior" + }, + "map": [ + { + "result": "neverInclude", + "match": 1 + }, + // '0' matches to SnippetsRule.Default. Means the behavior is decided by langauge. + // '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 unifed settings would persist '2' to storage when 'alwasyInclude' is selected. + { + "result": "alwaysInclude", + "match": 2 + }, + { + "result": "alwaysInclude", + "match": 0 + }, + { + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 3 + } + ] + } + } + }, + // CompletionOptionsStorage.EnterKeyBehavior + "textEditor.csharp.intellisense.returnKeyCompletionBehavior": { + "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "string", + "enum": [ "never", "afterFullyTypedWord", "always" ], + "enumItemLabels": [ "@Never_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], + "default": "never", + "order": 60, + "migration": { + "enumIntegerToString": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.EnterKeyBehavior" + }, + "map": [ + // '0' matches to EnterKeyRule.Default. Means the behavior is decided by langauge. + // '1' matches to SnippetsRule.Never. 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 '1' in front, so unifed settings would persist '2' to storage when 'never' is selected. + { + "result": "never", + "match": 1 + }, + { + "result": "never", + "match": 0 + }, + { + "result": "always", + "match": 2 + }, + { + "result": "afterFullyTypedWord", + "match": 3 + } + ] + } + } + }, + // CompletionOptionsStorage.ShowNameSuggestions + "textEditor.csharp.intellisense.showNameCompletionSuggestions": { + "title": "@Show_name_suggestions;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 70, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowNameSuggestions" + } + } + } + }, + // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces + "textEditor.csharp.intellisense.showCompletionItemsFromUnimportedNamespaces": { + "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": true, + "order": 80, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowItemsFromUnimportedNamespaces" + } + } + } + }, + // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets + "textEditor.csharp.intellisense.enableArgumentCompletionSnippets": { + "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", + "type": "boolean", + "default": false, + "order": 90, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.EnableArgumentCompletionSnippets" + } + } + } + }, + // CompletionOptionsStorage.ShowNewSnippetExperienceUserOption + "textEditor.csharp.intellisense.showNewSnippetExperience": { + "title": "@Show_new_snippet_experience_experimental;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "type": "boolean", + "default": false, + "alternateDefault": { + // CompletionOptionsStorage.ShowNewSnippetExperienceFeatureFlag + "flagName": "Roslyn.SnippetCompletion", + "default": true + }, + "order": 100, + "migration": { + "pass": { + "input": { + "store": "SettingsManager", + "path": "TextEditor.CSharp.Specific.ShowNewSnippetExperience" + } + } + } + } + }, + "categories": { + "textEditor.csharp":{ + "title": "C#" + }, + "textEditor.csharp.intellisense": { + "title": "@103;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", + "legacyOptionPageId": "EDE66829-7A36-4c5d-8E20-9290195DCF80" + } + } +} From e654161755fba0ec0b6fc3f1ccb39fa0bbbd3b62 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 14:51:37 -0800 Subject: [PATCH 0010/1047] Change it to VB content --- .../visualBasicSettings.registration.json | 156 +++++------------- 1 file changed, 45 insertions(+), 111 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json index 2253fef5a7a06..6c70a2e7ce7e4 100644 --- a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -1,8 +1,8 @@ { "properties": { // CompletionOptionsStorage.TriggerOnTypingLetters - "textEditor.csharp.intellisense.triggerCompletionOnTypingLetters": { - "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.visualBasic.intellisense.triggerCompletionOnTypingLetters": { + "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, "order": 0, @@ -10,100 +10,69 @@ "pass": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.TriggerOnTypingLetters" + "path": "TextEditor.VisualBasic.Specific.TriggerOnTypingLetters" } } } }, // CompletionOptionsStorage.TriggerOnDeletion - "textEditor.csharp.intellisense.triggerCompletionOnDeletion": { + "textEditor.visualBasic.intellisense.triggerCompletionOnDeletion": { "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", "type": "boolean", - "default": false, - "order": 1, - "enableWhen": "${config:textEditor.csharp.intellisense.triggerCompletionOnTypingLetters}=='true'", - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.TriggerOnDeletion" - } - } - } - }, - // CompletionOptionsStorage.TriggerInArgumentLists - "textEditor.csharp.intellisense.triggerCompletionInArgumentLists": { - "title": "@Automatically_show_completion_list_in_argument_lists;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", "default": true, - "order": 10, + "order": 1, "migration": { "pass": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.TriggerInArgumentLists" + "path": "TextEditor.VisualBasic.Specific.TriggerOnDeletion" } } } }, // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems - "textEditor.csharp.intellisense.highlightMatchingPortionsOfCompletionListItems": { - "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.visualBasic.intellisense.highlightMatchingPortionsOfCompletionListItems": { + "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, - "order": 20, + "order": 10, "migration": { "pass": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.HighlightMatchingPortionsOfCompletionListItems" + "path": "TextEditor.VisualBasic.Specific.HighlightMatchingPortionsOfCompletionListItems" } } } }, // CompletionViewOptionsStorage.ShowCompletionItemFilters - "textEditor.csharp.intellisense.showCompletionItemFilters": { - "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.visualBasic.intellisense.showCompletionItemFilters": { + "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, - "order": 30, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowCompletionItemFilters" - } - } - } - }, - // CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon - "textEditor.csharp.intellisense.completeStatementOnSemicolon": { - "title": "@Automatically_complete_statement_on_semicolon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 40, + "order": 20, "migration": { "pass": { "input": { "store": "SettingsManager", - "path": "TextEditor.AutomaticallyCompleteStatementOnSemicolon" + "path": "TextEditor.VisualBasic.Specific.ShowCompletionItemFilters" } } } }, // CompletionOptionsStorage.SnippetsBehavior - "textEditor.csharp.intellisense.snippetsBehavior": { - "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.visualBasic.intellisense.snippetsBehavior": { + "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "string", "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], "enumItemLabels": [ "@Never_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], - "default": "alwaysInclude", - "order": 50, + "default": "includeAfterTypingIdentifierQuestionTab", + "order": 30, "migration": { "enumIntegerToString": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.SnippetsBehavior" + "path": "TextEditor.VisualBasic.Specific.SnippetsBehavior" }, "map": [ { @@ -111,55 +80,55 @@ "match": 1 }, // '0' matches to SnippetsRule.Default. Means the behavior is decided by langauge. - // '2' matches to SnippetsRule.AlwaysInclude. It's the default behavior for C# + // '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 '2' in front, so unifed settings would persist '2' to storage when 'alwasyInclude' is selected. + // Put '3' in front, so unifed settings would persist '3' to storage when 'includeAfterTypingIdentifierQuestionTab' is selected. { "result": "alwaysInclude", "match": 2 }, { - "result": "alwaysInclude", - "match": 0 + "result": "includeAfterTypingIdentifierQuestionTab", + "match": 3 }, { "result": "includeAfterTypingIdentifierQuestionTab", - "match": 3 + "match": 0 } ] } } }, // CompletionOptionsStorage.EnterKeyBehavior - "textEditor.csharp.intellisense.returnKeyCompletionBehavior": { - "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.visualBasic.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;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], - "default": "never", - "order": 60, + "default": "always", + "order": 40, "migration": { "enumIntegerToString": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.EnterKeyBehavior" + "path": "TextEditor.VisualBasic.Specific.EnterKeyBehavior" }, "map": [ // '0' matches to EnterKeyRule.Default. Means the behavior is decided by langauge. - // '1' matches to SnippetsRule.Never. It's the default behavior for C# + // '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 '1' in front, so unifed settings would persist '2' to storage when 'never' is selected. + // Put '2' in front, so unifed settings would persist '2' to storage when 'always' is selected. { "result": "never", "match": 1 }, { - "result": "never", - "match": 0 + "result": "always", + "match": 2 }, { "result": "always", - "match": 2 + "match": 0 }, { "result": "afterFullyTypedWord", @@ -169,78 +138,43 @@ } } }, - // CompletionOptionsStorage.ShowNameSuggestions - "textEditor.csharp.intellisense.showNameCompletionSuggestions": { - "title": "@Show_name_suggestions;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": true, - "order": 70, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowNameSuggestions" - } - } - } - }, // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces - "textEditor.csharp.intellisense.showCompletionItemsFromUnimportedNamespaces": { - "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.visualBasic.intellisense.showCompletionItemsFromUnimportedNamespaces": { + "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, - "order": 80, + "order": 50, "migration": { "pass": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowItemsFromUnimportedNamespaces" + "path": "TextEditor.VisualBasic.Specific.ShowItemsFromUnimportedNamespaces" } } } }, // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets - "textEditor.csharp.intellisense.enableArgumentCompletionSnippets": { + "textEditor.visualBasic.intellisense.enableArgumentCompletionSnippets": { "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", "type": "boolean", "default": false, - "order": 90, - "migration": { - "pass": { - "input": { - "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.EnableArgumentCompletionSnippets" - } - } - } - }, - // CompletionOptionsStorage.ShowNewSnippetExperienceUserOption - "textEditor.csharp.intellisense.showNewSnippetExperience": { - "title": "@Show_new_snippet_experience_experimental;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", - "type": "boolean", - "default": false, - "alternateDefault": { - // CompletionOptionsStorage.ShowNewSnippetExperienceFeatureFlag - "flagName": "Roslyn.SnippetCompletion", - "default": true - }, - "order": 100, + "order": 60, "migration": { "pass": { "input": { "store": "SettingsManager", - "path": "TextEditor.CSharp.Specific.ShowNewSnippetExperience" + "path": "TextEditor.VisualBasic.Specific.EnableArgumentCompletionSnippets" } } } } }, "categories": { - "textEditor.csharp":{ - "title": "C#" + "textEditor.visualBasic":{ + "title": "Visual Basic" }, - "textEditor.csharp.intellisense": { - "title": "@103;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", + "textEditor.visualBasic.intellisense": { + "title": "@112;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "legacyOptionPageId": "EDE66829-7A36-4c5d-8E20-9290195DCF80" } } From 0f4402e888e3d13232915d4256bc9b7532be5eda Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 15:14:35 -0800 Subject: [PATCH 0011/1047] Rename to basic --- .../visualBasicSettings.registration.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json index 6c70a2e7ce7e4..ade07e8330726 100644 --- a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -1,7 +1,7 @@ { "properties": { // CompletionOptionsStorage.TriggerOnTypingLetters - "textEditor.visualBasic.intellisense.triggerCompletionOnTypingLetters": { + "textEditor.basic.intellisense.triggerCompletionOnTypingLetters": { "title": "@Show_completion_list_after_a_character_is_typed;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, @@ -16,8 +16,8 @@ } }, // CompletionOptionsStorage.TriggerOnDeletion - "textEditor.visualBasic.intellisense.triggerCompletionOnDeletion": { - "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.CSharp.dll", + "textEditor.basic.intellisense.triggerCompletionOnDeletion": { + "title": "@Show_completion_list_after_a_character_is_deleted;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, "order": 1, @@ -31,7 +31,7 @@ } }, // CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems - "textEditor.visualBasic.intellisense.highlightMatchingPortionsOfCompletionListItems": { + "textEditor.basic.intellisense.highlightMatchingPortionsOfCompletionListItems": { "title": "@Highlight_matching_portions_of_completion_list_items;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, @@ -46,7 +46,7 @@ } }, // CompletionViewOptionsStorage.ShowCompletionItemFilters - "textEditor.visualBasic.intellisense.showCompletionItemFilters": { + "textEditor.basic.intellisense.showCompletionItemFilters": { "title": "@Show_completion_item_filters;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, @@ -61,11 +61,11 @@ } }, // CompletionOptionsStorage.SnippetsBehavior - "textEditor.visualBasic.intellisense.snippetsBehavior": { + "textEditor.basic.intellisense.snippetsBehavior": { "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "string", "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], - "enumItemLabels": [ "@Never_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_include_snippets;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Include_snippets_when_Tab_is_typed_after_an_identifier;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], + "enumItemLabels": [ "@Never_include_snippets;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "@Always_include_snippets;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "@Include_snippets_when_Tab_is_typed_after_an_identifier;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll" ], "default": "includeAfterTypingIdentifierQuestionTab", "order": 30, "migration": { @@ -100,7 +100,7 @@ } }, // CompletionOptionsStorage.EnterKeyBehavior - "textEditor.visualBasic.intellisense.returnKeyCompletionBehavior": { + "textEditor.basic.intellisense.returnKeyCompletionBehavior": { "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "string", "enum": [ "never", "afterFullyTypedWord", "always" ], @@ -139,7 +139,7 @@ } }, // CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces - "textEditor.visualBasic.intellisense.showCompletionItemsFromUnimportedNamespaces": { + "textEditor.basic.intellisense.showCompletionItemsFromUnimportedNamespaces": { "title": "@Show_items_from_unimported_namespaces;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "boolean", "default": true, @@ -154,7 +154,7 @@ } }, // CompletionViewOptionsStorage.EnableArgumentCompletionSnippets - "textEditor.visualBasic.intellisense.enableArgumentCompletionSnippets": { + "textEditor.basic.intellisense.enableArgumentCompletionSnippets": { "title": "@Tab_twice_to_insert_arguments;..\\Microsoft.VisualStudio.LanguageServices.dll", "type": "boolean", "default": false, @@ -170,10 +170,10 @@ } }, "categories": { - "textEditor.visualBasic":{ + "textEditor.basic":{ "title": "Visual Basic" }, - "textEditor.visualBasic.intellisense": { + "textEditor.basic.intellisense": { "title": "@112;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", "legacyOptionPageId": "EDE66829-7A36-4c5d-8E20-9290195DCF80" } From c3ef0615258f92fc9d8560ebb145e3b15f739688 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 8 Mar 2024 16:15:16 -0800 Subject: [PATCH 0012/1047] Put resources in package resources --- .../visualBasicSettings.registration.json | 6 ++-- .../VisualBasic/Impl/VSPackage.resx | 18 +++++++++++ .../VisualBasic/Impl/xlf/VSPackage.cs.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.de.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.es.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.fr.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.it.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.ja.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.ko.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.pl.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.ru.xlf | 30 +++++++++++++++++++ .../VisualBasic/Impl/xlf/VSPackage.tr.xlf | 30 +++++++++++++++++++ .../Impl/xlf/VSPackage.zh-Hans.xlf | 30 +++++++++++++++++++ .../Impl/xlf/VSPackage.zh-Hant.xlf | 30 +++++++++++++++++++ 15 files changed, 411 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json index ade07e8330726..b4565d81360a7 100644 --- a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -65,7 +65,7 @@ "title": "@Snippets_behavior;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "string", "enum": [ "neverInclude", "alwaysInclude", "includeAfterTypingIdentifierQuestionTab" ], - "enumItemLabels": [ "@Never_include_snippets;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "@Always_include_snippets;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "@Include_snippets_when_Tab_is_typed_after_an_identifier;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll" ], + "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": { @@ -79,7 +79,7 @@ "result": "neverInclude", "match": 1 }, - // '0' matches to SnippetsRule.Default. Means the behavior is decided by langauge. + // '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. @@ -104,7 +104,7 @@ "title": "@Enter_key_behavior_colon;..\\Microsoft.VisualStudio.LanguageServices.VisualBasic.dll", "type": "string", "enum": [ "never", "afterFullyTypedWord", "always" ], - "enumItemLabels": [ "@Never_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Only_add_new_line_on_enter_after_end_of_fully_typed_word;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}", "@Always_add_new_line_on_enter;{13c3bbb4-f18f-4111-9f54-a0fb010d9194}" ], + "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": { diff --git a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx index 5d08b3c624aa9..0b793eb6682c6 100644 --- a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx +++ b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx @@ -214,4 +214,22 @@ Use enhanced colors;Editor Color Scheme;Inheritance Margin;Import Directives; An empty Visual Basic script file. + + 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/VSPackage.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf index 3e480ae5e0d4f..b777e51b2818b 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Prázdný soubor skriptu Visual Basicu + + 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 + + Visual Basic Script Skript Visual Basicu diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf index e3e7071dc4130..1873daf4c5d7a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Eine leere Visual Basic-Skriptdatei. + + 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 + + Visual Basic Script Visual Basic-Skript diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf index d9aca8082b72e..82579f109165d 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Un archivo de script de Visual Basic vacío. + + 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 + + Visual Basic Script Script de Visual Basic diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf index 4a10289b48ac6..42563e8a2376f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Fichier de script Visual Basic vide. + + 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 + + Visual Basic Script Script Visual Basic diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf index f212918f97dd0..05accaa59941e 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. File script di Visual Basic vuoto. + + 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 + + Visual Basic Script Script di Visual Basic diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf index 7bf1f9f4c24be..a31efbd03dec8 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf @@ -130,11 +130,41 @@ JSON 文字列のエディター機能の検出と提供; Visual Basic ツール Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + An empty Visual Basic script file. 空の Visual Basic スクリプト ファイル。 + + 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 + + Visual Basic Script Visual Basic スクリプト diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf index dd3ec505f7e48..85bdf889d5716 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf @@ -130,11 +130,41 @@ JSON 문자열 색상 지정, Visual Basic 도구 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + An empty Visual Basic script file. 비어 있는 Visual Basic 스크립트 파일입니다. + + 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 + + Visual Basic Script Visual Basic 스크립트 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf index 1018c8a1f8bf9..9447aef3a76fe 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Pusty plik skryptu języka Visual Basic. + + 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 + + Visual Basic Script Skrypt języka Visual Basic diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf index f9d156fa540ba..c3f19f2b9de76 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Um arquivo de script vazio do Visual Basic. + + 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 + + Visual Basic Script Script do Visual Basic diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf index ab8f5e44b536f..9f70116b86c8e 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf @@ -130,11 +130,41 @@ JSON; Инструменты Visual Basic Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + An empty Visual Basic script file. Пустой файл сценария Visual Basic. + + 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 + + Visual Basic Script Сценарий Visual Basic diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf index 81e4a19a73a15..3cb7fe4120bd5 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf @@ -130,11 +130,41 @@ 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 + + An empty Visual Basic script file. Boş bir Visual Basic betik dosyası. + + 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 + + Visual Basic Script Visual Basic Betiği diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf index 403ed32122a6e..d4bb49956fb72 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf @@ -130,11 +130,41 @@ JSON; Visual Basic 工具 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + An empty Visual Basic script file. 空的 Visual Basic 脚本文件。 + + 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 + + Visual Basic Script Visual Basic 脚本 diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf index b6324ad3cf1f8..e336be864f31b 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf @@ -130,11 +130,41 @@ JSON; Visual Basic 工具 Help > About + + Always add new line on enter + Always add new line on enter + + + + Always include snippets + Always include snippets + + An empty Visual Basic script file. 空白的 Visual Basic 指令碼檔。 + + 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 + + Visual Basic Script Visual Basic 指令碼 From 189299e42302edd97afde1f9fcc582040a9fdad7 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 13 Mar 2024 14:12:47 -0700 Subject: [PATCH 0013/1047] Clean up after rebasing --- eng/SourceBuildPrebuiltBaseline.xml | 1 - src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 7caa4e4c36196..592d3954059e8 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -41,6 +41,5 @@ - diff --git a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef index 79d2739217e87..ee45c4813cee4 100644 --- a/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/CSharp/Impl/PackageRegistration.pkgdef @@ -195,7 +195,7 @@ // 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" +[$RootKey$\SettingsManifests\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] +@="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" "CacheTag"=qword:08DC1824DFE0117B From 6b326464b4c0db46df970cd5d3bc439ab409787b Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 13 Mar 2024 14:14:10 -0700 Subject: [PATCH 0014/1047] Add comment --- .../UnifiedSettings/visualBasicSettings.registration.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json index b4565d81360a7..7bba9c4161e1b 100644 --- a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -1,3 +1,7 @@ +// 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 From a3393690ff423fa61d59f822d9e9fe635df7e7aa Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 13 Mar 2024 15:11:02 -0700 Subject: [PATCH 0015/1047] Change the package entry --- .../VisualBasic/Impl/PackageRegistration.pkgdef | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index 8179de4f741c4..e49f42d24d23f 100644 --- a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef @@ -192,7 +192,7 @@ // 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\{13c3bbb4-f18f-4111-9f54-a0fb010d9194}] -@="Microsoft.VisualStudio.LanguageServices.CSharp.LanguageService.CSharpPackage" -"ManifestPath"="$PackageFolder$\UnifiedSettings\csharpSettings.registration.json" +[$RootKey$\SettingsManifests\{574fc912-f74f-4b4e-92c3-f695c208a2bb}] +@="Microsoft.VisualStudio.LanguageServices.VisualBasic.VisualBasicPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\visualBasicSettings.registration.json" "CacheTag"=qword:08DC1824DFE0117B From 88170e021e29880fdbce925d4da0a0853d43d26b Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Tue, 19 Mar 2024 14:22:22 -0700 Subject: [PATCH 0016/1047] Merge part of the resources --- .../VisualBasic/Impl/BasicVSResources.resx | 9 --------- ...sualStudio.LanguageServices.VisualBasic.vbproj | 2 +- .../Impl/Options/IntelliSenseOptionPageStrings.vb | 6 +++--- .../VisualBasic/Impl/xlf/BasicVSResources.cs.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.de.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.es.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.fr.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.it.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.ja.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.ko.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.pl.xlf | 15 --------------- .../Impl/xlf/BasicVSResources.pt-BR.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.ru.xlf | 15 --------------- .../VisualBasic/Impl/xlf/BasicVSResources.tr.xlf | 15 --------------- .../Impl/xlf/BasicVSResources.zh-Hans.xlf | 15 --------------- .../Impl/xlf/BasicVSResources.zh-Hant.xlf | 15 --------------- 16 files changed, 4 insertions(+), 208 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx index 3c1c089e2bbdb..3a141afacab6f 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/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj index d12fd4d5c7671..b94764b606b87 100644 --- a/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj +++ b/src/VisualStudio/VisualBasic/Impl/Microsoft.VisualStudio.LanguageServices.VisualBasic.vbproj @@ -55,7 +55,7 @@ - + true VSPackage Designer 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/xlf/BasicVSResources.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf index 6754b224ea9b5..7c07b4634566c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf @@ -277,21 +277,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 df0ed3b678c4d..2be453fbf348c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf @@ -277,21 +277,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 4cee6d81709ef..12f62f4d74e2f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf @@ -277,21 +277,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 5f44e86f2b776..5a4f27aa80806 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf @@ -277,21 +277,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 b61b1d3b6dd23..b828ad360b3fb 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf @@ -277,21 +277,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 282b439aaa355..cdf2be1b5356c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf @@ -277,21 +277,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 9449f6e04513b..92d20a00e0478 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf @@ -277,21 +277,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 220adee27b170..a33726c80a376 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf @@ -277,21 +277,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 801d59f2a2d99..5b3be1246c514 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf @@ -277,21 +277,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 bdbdb33241570..86947b68eba62 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf @@ -277,21 +277,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 20749fed8fe6b..387d254ca4a90 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf @@ -277,21 +277,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 a1723e5a2642e..512f096fd987d 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf @@ -277,21 +277,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 5c73f3da6c0ff..0a51b84c9bf8e 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf @@ -277,21 +277,6 @@ 永不在按下 Enter 鍵時加入新行(_N) - - Always include snippets - 一律包含程式碼片段 - - - - Include snippets when ?-Tab is typed after an identifier - 在識別碼後輸入 ?-Tab 時包含程式碼片段 - - - - Never include snippets - 一律不包含程式碼片段 - - Snippets behavior 程式碼片段行為 From ea58a2e5d977f4e43b93241680dc426f5a70a578 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 22 Mar 2024 13:14:26 +0000 Subject: [PATCH 0017/1047] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24170.6 --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5c9864415aeaa..0bc2088df684a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - f311667e0587f19c3fa9553a909975662107a351 + 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - f311667e0587f19c3fa9553a909975662107a351 + 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 https://github.com/dotnet/roslyn-analyzers diff --git a/global.json b/global.json index 59843f320d926..a6544a313da8d 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24165.4", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24165.4" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24170.6", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24170.6" } } From bba3f9b16df523154c00aebd953ef065fed3b742 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 23 Mar 2024 13:04:27 +0000 Subject: [PATCH 0018/1047] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24170.6 From 1004fc0fd40bd31900901b0b0c66eb8ca29904e3 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 24 Mar 2024 12:55:32 +0000 Subject: [PATCH 0019/1047] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24170.6 From b27c081970d34a56937e544110931959ad10e005 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 25 Mar 2024 13:02:10 +0000 Subject: [PATCH 0020/1047] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24172.5 --- eng/Version.Details.xml | 8 ++++---- eng/common/templates-official/job/job.yml | 2 +- global.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0bc2088df684a..eefbb37e7aeef 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 + ceb071c1060b8e6de404c065b4045442570caa18 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 + ceb071c1060b8e6de404c065b4045442570caa18 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index a2709d10562ca..0604277a2ff52 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -128,7 +128,7 @@ jobs: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - task: MicroBuildSigningPlugin@3 + - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin inputs: signType: $(_SignType) diff --git a/global.json b/global.json index a6544a313da8d..53537d5f1f61b 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24170.6", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24170.6" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24172.5", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24172.5" } } From c5c1507183f8c0db506f1de26342079dc4c19095 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 27 Mar 2024 13:39:17 +0000 Subject: [PATCH 0021/1047] Update dependencies from https://github.com/dotnet/arcade build 20240326.8 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24176.8 --- eng/Version.Details.xml | 8 ++++---- eng/common/templates-official/job/job.yml | 1 + global.json | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index eefbb37e7aeef..cf8de09b2e66c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - ceb071c1060b8e6de404c065b4045442570caa18 + 48e9e0d2164de0535446809364724da8962123a6 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - ceb071c1060b8e6de404c065b4045442570caa18 + 48e9e0d2164de0535446809364724da8962123a6 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 0604277a2ff52..1f035fee73f4a 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -136,6 +136,7 @@ jobs: feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json env: TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) diff --git a/global.json b/global.json index 53537d5f1f61b..89aa5199fee2f 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24172.5", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24172.5" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24176.8", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24176.8" } } From 4fec197abc2346aa24208dd675b1a13200dfbe98 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 28 Mar 2024 13:28:11 +0000 Subject: [PATCH 0022/1047] Update dependencies from https://github.com/dotnet/arcade build 20240327.1 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24177.1 --- eng/Version.Details.xml | 8 ++++---- eng/common/native/init-compiler.sh | 2 +- global.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index cf8de09b2e66c..bf6583ca6fb11 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - 48e9e0d2164de0535446809364724da8962123a6 + b17b2a1bb7da23253043dee059f374b00f3e321a @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 48e9e0d2164de0535446809364724da8962123a6 + b17b2a1bb7da23253043dee059f374b00f3e321a https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index f5c1ec7eafeb2..2d5660642b8d4 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -63,7 +63,7 @@ if [ -z "$CLR_CC" ]; then # Set default versions if [ -z "$majorVersion" ]; then # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [ "$compiler" = "clang" ]; then versions="17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" + if [ "$compiler" = "clang" ]; then versions="18 17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" elif [ "$compiler" = "gcc" ]; then versions="13 12 11 10 9 8 7 6 5 4.9"; fi for version in $versions; do diff --git a/global.json b/global.json index 89aa5199fee2f..1b809102d42df 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24176.8", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24176.8" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24177.1", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24177.1" } } From fdd0d445812dff39b068e2a30078721c23addabd Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 29 Mar 2024 13:13:20 +0000 Subject: [PATCH 0023/1047] Update dependencies from https://github.com/dotnet/arcade build 20240327.1 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24177.1 From c660b67b7a07a18b6a8825743d87abc5e8505f65 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 30 Mar 2024 13:22:12 +0000 Subject: [PATCH 0024/1047] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 --- eng/Version.Details.xml | 8 ++++---- eng/common/templates-official/job/onelocbuild.yml | 2 +- .../templates-official/job/publish-build-assets.yml | 4 ++-- eng/common/templates-official/job/source-build.yml | 2 +- .../templates-official/job/source-index-stage1.yml | 2 +- .../templates-official/post-build/post-build.yml | 10 +++++----- .../templates-official/variables/pool-providers.yml | 2 +- global.json | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index bf6583ca6fb11..2b46d03cd1241 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - b17b2a1bb7da23253043dee059f374b00f3e321a + fc2b7849b25c4a21457feb6da5fc7c9806a80976 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - b17b2a1bb7da23253043dee059f374b00f3e321a + fc2b7849b25c4a21457feb6da5fc7c9806a80976 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/job/onelocbuild.yml b/eng/common/templates-official/job/onelocbuild.yml index ba9ba49303292..52b4d05d3f8dd 100644 --- a/eng/common/templates-official/job/onelocbuild.yml +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -56,7 +56,7 @@ jobs: # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml index 53138622fe7a3..589ac80a18b74 100644 --- a/eng/common/templates-official/job/publish-build-assets.yml +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -60,8 +60,8 @@ jobs: os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 os: windows steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml index 8aba3b44bb252..f193dfbe23668 100644 --- a/eng/common/templates-official/job/source-build.yml +++ b/eng/common/templates-official/job/source-build.yml @@ -52,7 +52,7 @@ jobs: ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - image: 1es-mariner-2-pt + image: 1es-mariner-2 os: linux ${{ if ne(parameters.platform.pool, '') }}: diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml index 4b63373917085..f0513aee5b0da 100644 --- a/eng/common/templates-official/job/source-index-stage1.yml +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -33,7 +33,7 @@ jobs: demands: ImageOverride -equals windows.vs2019.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: windows.vs2022.amd64 os: windows steps: diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml index 5c98fe1c0f3a9..da1f40958b450 100644 --- a/eng/common/templates-official/post-build/post-build.yml +++ b/eng/common/templates-official/post-build/post-build.yml @@ -110,7 +110,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: @@ -150,7 +150,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -208,7 +208,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -261,8 +261,8 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 os: windows steps: - template: setup-maestro-vars.yml diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml index beab7d1bfba06..1f308b24efc43 100644 --- a/eng/common/templates-official/variables/pool-providers.yml +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -23,7 +23,7 @@ # # pool: # name: $(DncEngInternalBuildPool) -# image: 1es-windows-2022-pt +# image: 1es-windows-2022 variables: # Coalesce the target and source branches so we know when a PR targets a release branch diff --git a/global.json b/global.json index 1b809102d42df..d1af99caa9c33 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24177.1", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24177.1" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24179.4", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24179.4" } } From 12e1e039bce5d89178913bbccc44b8db108957a8 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Tue, 2 Apr 2024 12:33:11 +0000 Subject: [PATCH 0025/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 94ec343a3348a..edc7832c1bc5e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,14 +13,14 @@ c0b5d69a1a1513528c77fffff708c7502d57c35c - + https://github.com/dotnet/command-line-api - 5ea97af07263ea3ef68a18557c8aa3f7e3200bda + c96672b8b84c307feb035fed6cbe9db85d5b87d3 - + https://github.com/dotnet/command-line-api - 5ea97af07263ea3ef68a18557c8aa3f7e3200bda + c96672b8b84c307feb035fed6cbe9db85d5b87d3 diff --git a/eng/Versions.props b/eng/Versions.props index e15afb3c04ac0..240ddbdc9dad9 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,7 +20,7 @@ Versions managed by Arcade (see Versions.Details.xml) --> - 2.0.0-beta4.24126.1 + 2.0.0-beta4.24201.1 8.0.0 8.0.0 8.0.0 From caba8b2b18987464a81625eea3603dd5073c991d Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Tue, 2 Apr 2024 12:22:13 -0700 Subject: [PATCH 0026/1047] Move Replay to the proper foler in solution explorer --- Roslyn.sln | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Roslyn.sln b/Roslyn.sln index b51fb31aa5b84..f99a5eef770c7 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -545,10 +545,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Feat EndProject 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("{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}" -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}" @@ -1625,8 +1621,7 @@ Global {5BABC440-4F1B-46E8-9068-DD7F02ED25D3} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} {5762E483-75CE-4328-A410-511F30737712} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} {6D819E80-BA2F-4317-8368-37F8F4434D3A} = {8977A560-45C2-4EC2-A849-97335B382C74} - {47D004BE-F797-430E-8A18-4B0CDFD56643} = {0F3118AE-8D36-4384-8E80-BD6566365305} - {DB96C25F-39A9-4A6A-92BC-D1E42717308F} = {47D004BE-F797-430E-8A18-4B0CDFD56643} + {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} From 8fb8343205f4fa82bf3de97bd93365d4c524e944 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 3 Apr 2024 12:30:50 +0000 Subject: [PATCH 0027/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From 83cd1c3693f5e5e67b0fa7aa05160b958e4d4acf Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 3 Apr 2024 13:12:02 +0000 Subject: [PATCH 0028/1047] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 From b93fbabc75d23c852c491775cc3ba2c24243dfbf Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 4 Apr 2024 12:22:16 +0000 Subject: [PATCH 0029/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From 6b0cf7cdf8fb6d4078362be7eb9493cf9dfa2302 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 4 Apr 2024 12:59:28 +0000 Subject: [PATCH 0030/1047] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 From da22b4b78837429d57e266bd04e7ec59045b2680 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 4 Apr 2024 14:35:16 -0700 Subject: [PATCH 0031/1047] Add the the option storgage and dicttionary --- .../Def/Options/VisualStudioOptionStorage.cs | 45 +++++++++++++++++++ .../Options/InternalOptionStorageMapping.cs | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 04342100e7d9e..e590591bf5e66 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -437,4 +437,49 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_enable_opening_source_generated_files_in_workspace_feature_flag", new FeatureFlagStorage(@"Roslyn.SourceGeneratorsEnableOpeningInWorkspace")}, {"xaml_enable_lsp_intellisense", new FeatureFlagStorage(@"Xaml.EnableLspIntelliSense")}, }; + + #region UnifiedSettings + + internal sealed class UnifiedSettingsStorage : VisualStudioOptionStorage + { + private const string LanguagePlaceholder = "%LANGUAGE%"; + + /// + /// C# name used in Unified Settings path. + /// + private const string csharpKey = "csharp"; + + /// + /// Visual Basic name used in Unified Settings path. + /// + private const string visualBasicKey = "basic"; + + /// + /// Unified settings base path, might contains %LANGAUGE% if it maps to two per-language different setting. + /// + public string UnifiedSettingsBasePath { get; init; } + + public string GetUnifiedSettingsPath(string language) + { + if (!UnifiedSettingsBasePath.Contains(LanguagePlaceholder)) + { + return UnifiedSettingsBasePath; + } + + return language switch + { + LanguageNames.CSharp => UnifiedSettingsBasePath.Replace(LanguagePlaceholder, csharpKey), + LanguageNames.VisualBasic => UnifiedSettingsBasePath.Replace(LanguagePlaceholder, visualBasicKey), + // Current we don't expect to handle other languages + _ => throw ExceptionUtilities.UnexpectedValue(language) + }; + } + } + + public static readonly IReadOnlyDictionary UnifiedSettingsStorages = new Dictionary() + { + { "dotnet_trigger_completion_on_typing_letters", new UnifiedSettingsStorage() { UnifiedSettingsBasePath = "textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters"} } + }; + + #endregion } 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); From 940d4ce5f5554bbbca731b87ee04053ea6c1be07 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 4 Apr 2024 14:48:06 -0700 Subject: [PATCH 0032/1047] Add empty unit test file --- ...o.LanguageServices.CSharp.UnitTests.csproj | 3 +++ .../CSharpUnifiedSettingsTests.cs | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs 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..eb4d44e8a02fc 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -77,6 +77,9 @@ PreserveNewest + + + diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs new file mode 100644 index 0000000000000..bdf68ff04da02 --- /dev/null +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Roslyn.VisualStudio.CSharp.UnitTests.UnifiedSettings +{ + public class CSharpUnifiedSettingsTests + { + [Fact] + public void Test() + { + + } + } +} From 8e0c981bd1624ecf4555d45dd24806d958f3b22c Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 4 Apr 2024 15:27:10 -0700 Subject: [PATCH 0033/1047] Add the basic structure --- ...o.LanguageServices.CSharp.UnitTests.csproj | 2 +- .../CSharpUnifiedSettingsTests.cs | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) 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 eb4d44e8a02fc..4db7c4fd916a8 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -78,7 +78,7 @@ - + diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index bdf68ff04da02..35625ff01d993 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -4,19 +4,44 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices.Options; +using Roslyn.Utilities; using Xunit; namespace Roslyn.VisualStudio.CSharp.UnitTests.UnifiedSettings { public class CSharpUnifiedSettingsTests { + private readonly static ImmutableArray s_onboardedOptions = ImmutableArray.Create(CompletionOptionsStorage.TriggerOnTypingLetters); + [Fact] - public void Test() + public async Task CSharpUnifiedSettingsTest() { + 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); + + foreach (var option in s_onboardedOptions) + { + var optionName = option.Definition.ConfigName; + if (VisualStudioOptionStorage.UnifiedSettingsStorages.TryGetValue(optionName, out var unifiedSettingsStorage)) + { + } + else + { + // Can't find the option in the storage dictionary + throw ExceptionUtilities.UnexpectedValue(optionName); + } + } } } } From 9c37deeadf91d0848d4ff09d4d079499cc2c3ee7 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 4 Apr 2024 16:04:27 -0700 Subject: [PATCH 0034/1047] Use JsonPath to verify --- .../CSharpUnifiedSettingsTests.cs | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 35625ff01d993..efc60c444e13f 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -10,9 +10,15 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.CSharp.Snippets; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.UnitTests; +using Microsoft.VisualStudio.CallHierarchy.Package.Definitions; using Microsoft.VisualStudio.LanguageServices.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Roslyn.Utilities; using Xunit; @@ -23,25 +29,42 @@ public class CSharpUnifiedSettingsTests private readonly static ImmutableArray s_onboardedOptions = ImmutableArray.Create(CompletionOptionsStorage.TriggerOnTypingLetters); [Fact] - public async Task CSharpUnifiedSettingsTest() + public async Task CSharpIntellisensePageTest() { 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#", categoriesTitle.ToString()); + var optionPageId = registrationJsonObject.SelectToken("$.categories.['textEditor.csharp.intellisense'].legacyOptionPageId")!; + Assert.Equal(Microsoft.VisualStudio.LanguageServices.Guids.CSharpOptionPageIntelliSenseIdString, optionPageId.ToString()); + + //foreach (var option in s_onboardedOptions) + //{ + // var optionName = option.Definition.ConfigName; + // if (VisualStudioOptionStorage.UnifiedSettingsStorages.TryGetValue(optionName, out var unifiedSettingsStorage)) + // { + // var unifiedSettingsPath = unifiedSettingsStorage.GetUnifiedSettingsPath(LanguageNames.CSharp); + + // } + // else + // { + // // Can't find the option in the storage dictionary + // throw ExceptionUtilities.UnexpectedValue(optionName); + // } + //} + } + + private static void VerifyType() + { + + } + + private static void VerifyDefaultValue() + { - foreach (var option in s_onboardedOptions) - { - var optionName = option.Definition.ConfigName; - if (VisualStudioOptionStorage.UnifiedSettingsStorages.TryGetValue(optionName, out var unifiedSettingsStorage)) - { - - } - else - { - // Can't find the option in the storage dictionary - throw ExceptionUtilities.UnexpectedValue(optionName); - } - } } } } From 4b70eeb0eea88222636710a748383578fc8cb6c9 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 5 Apr 2024 12:17:34 +0000 Subject: [PATCH 0035/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From cbe81264f509a58a52304c0f0e1d84ab76a31525 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 5 Apr 2024 12:46:55 +0000 Subject: [PATCH 0036/1047] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 From 360d0f4e982019af3320ab6f4b1cffe77c3d7f21 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 12:19:23 -0700 Subject: [PATCH 0037/1047] Add Verify DefaultValue --- .../CSharpUnifiedSettingsTests.cs | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index efc60c444e13f..42f5bb80de9e1 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -32,37 +32,54 @@ public class CSharpUnifiedSettingsTests public async Task CSharpIntellisensePageTest() { var registrationFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.csharpSettings.registration.json"); - using var reader = new StreamReader(registrationFileStream); + 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")!; + var categoriesTitle = registrationJsonObject.SelectToken("$.categories['textEditor.csharp'].title")!; Assert.Equal("C#", categoriesTitle.ToString()); - var optionPageId = registrationJsonObject.SelectToken("$.categories.['textEditor.csharp.intellisense'].legacyOptionPageId")!; + var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId")!; Assert.Equal(Microsoft.VisualStudio.LanguageServices.Guids.CSharpOptionPageIntelliSenseIdString, optionPageId.ToString()); - //foreach (var option in s_onboardedOptions) - //{ - // var optionName = option.Definition.ConfigName; - // if (VisualStudioOptionStorage.UnifiedSettingsStorages.TryGetValue(optionName, out var unifiedSettingsStorage)) - // { - // var unifiedSettingsPath = unifiedSettingsStorage.GetUnifiedSettingsPath(LanguageNames.CSharp); - - // } - // else - // { - // // Can't find the option in the storage dictionary - // throw ExceptionUtilities.UnexpectedValue(optionName); - // } - //} + foreach (var option in s_onboardedOptions) + { + var optionName = option.Definition.ConfigName; + if (VisualStudioOptionStorage.UnifiedSettingsStorages.TryGetValue(optionName, out var unifiedSettingsStorage)) + { + var unifiedSettingsPath = unifiedSettingsStorage.GetUnifiedSettingsPath(LanguageNames.CSharp); + VerifyType(registrationJsonObject, unifiedSettingsPath, option); + VerifyDefaultValue(registrationJsonObject, unifiedSettingsPath, option); + } + else + { + // Can't find the option in the storage dictionary + throw ExceptionUtilities.UnexpectedValue(optionName); + } + } } - private static void VerifyType() + private static void VerifyType(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { + var actualType = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].type")!; + var expectedType = ConvertTypeNameToJsonType(option.Definition.Type.Name); + Assert.Equal(expectedType, actualType.ToString()); + + static string ConvertTypeNameToJsonType(string typeName) + => typeName switch + { + "Boolean" => "boolean", + _ => throw ExceptionUtilities.Unreachable() + }; + } + private static void VerifyDefaultValue(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option, string? alternateDefaultOnNull = null) + { + var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; + var expectedDefaultValue = option.Definition.DefaultValue?.ToString() ?? alternateDefaultOnNull; + Assert.Equal(actualDefaultValue, expectedDefaultValue); } - private static void VerifyDefaultValue() + private static void VerifyMigration() { } From 297faba631411cb0603663c6a36fc9881858d5ea Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 15:28:45 -0700 Subject: [PATCH 0038/1047] Verify pass --- .../CSharpUnifiedSettingsTests.cs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 42f5bb80de9e1..a6cd4993111eb 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -49,6 +49,7 @@ public async Task CSharpIntellisensePageTest() var unifiedSettingsPath = unifiedSettingsStorage.GetUnifiedSettingsPath(LanguageNames.CSharp); VerifyType(registrationJsonObject, unifiedSettingsPath, option); VerifyDefaultValue(registrationJsonObject, unifiedSettingsPath, option); + VerifyMigration(registrationJsonObject, unifiedSettingsPath, option); } else { @@ -79,9 +80,61 @@ private static void VerifyDefaultValue(JObject registrationJsonObject, string un Assert.Equal(actualDefaultValue, expectedDefaultValue); } - private static void VerifyMigration() + private static void VerifyMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { + var actualMigration = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration")!; + // Get the single property under migration + var migrationProperty = (JProperty)actualMigration.Children().Single(); + var migrationType = migrationProperty.Name; + if (migrationType is "pass") + { + // migration: { + // pass: { + // input: { + // } + // } + // } + // Verify input node + var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.pass.input")!; + VerifyInput(input, option); + } + else if (migrationType is "enumIntegerToString") + { + VerifyEnumIntegerToStringMigration(registrationJsonObject, unifiedSettingPath, option); + } + else + { + // Need adding more migration types if new type is added + throw ExceptionUtilities.UnexpectedValue(migrationType); + } + + // Verify input property under migration + // "input": { + // "store": "xxxx", + // "path": "yyyy" + // } + static void VerifyInput(JToken input, IOption2 option) + { + var store = input.SelectToken("store")!.ToString(); + var path = input.SelectToken("path")!.ToString(); + var configName = option.Definition.ConfigName; + var visualStudioStorage = VisualStudioOptionStorage.Storages[configName]; + if (visualStudioStorage is VisualStudioOptionStorage.RoamingProfileStorage roamingProfileStorage) + { + Assert.Equal("SettingsManager", store); + Assert.Equal(roamingProfileStorage.Key.Replace("%LANGUAGE%", "CSharp"), path); + } + else + { + // Not supported yet + throw ExceptionUtilities.Unreachable(); + } + } + static void VerifyEnumIntegerToStringMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) + { + var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.pass.input")!; + } } } } From 6a17e810d1afb5dd14567b68a132e14099ad1200 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 17:32:24 -0700 Subject: [PATCH 0039/1047] Refactor the test --- .../CSharpUnifiedSettingsTests.cs | 98 ++++++++++++++----- .../Def/Options/VisualStudioOptionStorage.cs | 3 +- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index a6cd4993111eb..a5dbc6086a86a 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -2,22 +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.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.CSharp.Snippets; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.UnitTests; -using Microsoft.VisualStudio.CallHierarchy.Package.Definitions; using Microsoft.VisualStudio.LanguageServices.Options; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Roslyn.Utilities; using Xunit; @@ -26,15 +19,25 @@ namespace Roslyn.VisualStudio.CSharp.UnitTests.UnifiedSettings { public class CSharpUnifiedSettingsTests { - private readonly static ImmutableArray s_onboardedOptions = ImmutableArray.Create(CompletionOptionsStorage.TriggerOnTypingLetters); + private static readonly ImmutableArray s_onboardedOptions = ImmutableArray.Create( + CompletionOptionsStorage.TriggerOnTypingLetters, + CompletionOptionsStorage.SnippetsBehavior); + + /// + /// Some options use different default value for C# and VB. The default value in OptionConfig is just a stub value. + /// The real value is set at runtime. But in unified settings we always use the correct value for language. + /// Use this dictionary to indicate that in unit test. + /// + private static readonly ImmutableDictionary s_optionsToDefaultValue = ImmutableDictionary.Empty + .Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude.ToString().ToCamelCase()); [Fact] - public async Task CSharpIntellisensePageTest() + public async Task IntellisensePageSettingsTest() { 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 registrationJsonObject = JObject.Parse(registrationFile, new JsonLoadSettings { CommentHandling = CommentHandling.Ignore }); var categoriesTitle = registrationJsonObject.SelectToken("$.categories['textEditor.csharp'].title")!; Assert.Equal("C#", categoriesTitle.ToString()); @@ -48,8 +51,14 @@ public async Task CSharpIntellisensePageTest() { var unifiedSettingsPath = unifiedSettingsStorage.GetUnifiedSettingsPath(LanguageNames.CSharp); VerifyType(registrationJsonObject, unifiedSettingsPath, option); - VerifyDefaultValue(registrationJsonObject, unifiedSettingsPath, option); - VerifyMigration(registrationJsonObject, unifiedSettingsPath, option); + if (option.Type.IsEnum) + { + // VerifyEnum(registrationJsonObject, unifiedSettingsPath, option); + } + else + { + VerifySettings(registrationJsonObject, unifiedSettingsPath, option); + } } else { @@ -59,25 +68,56 @@ public async Task CSharpIntellisensePageTest() } } + private static void VerifySettings(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) + { + // Verify default value + var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; + var expectedDefaultValue = option.Definition.DefaultValue?.ToString(); + Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue); + VerifyMigration(registrationJsonObject, unifiedSettingPath, option); + } + + private static void VerifyEnum(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) + { + var enumArray = registrationJsonObject.SelectTokens($"$.properties['{unifiedSettingPath}'].enum"); + + } + private static void VerifyType(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { var actualType = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].type")!; - var expectedType = ConvertTypeNameToJsonType(option.Definition.Type.Name); - Assert.Equal(expectedType, actualType.ToString()); + var expectedType = option.Definition.Type; + if (expectedType.IsEnum) + { + // Enum is string in json + Assert.Equal("string", actualType.ToString()); + } + else + { + var expectedTypeName = ConvertTypeNameToJsonType(option.Definition.Type.Name); + Assert.Equal(expectedTypeName, actualType.ToString()); + } static string ConvertTypeNameToJsonType(string typeName) => typeName switch { "Boolean" => "boolean", - _ => throw ExceptionUtilities.Unreachable() + _ => typeName }; } - private static void VerifyDefaultValue(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option, string? alternateDefaultOnNull = null) + private static void VerifyDefaultValue(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; - var expectedDefaultValue = option.Definition.DefaultValue?.ToString() ?? alternateDefaultOnNull; - Assert.Equal(actualDefaultValue, expectedDefaultValue); + if (s_optionsToDefaultValue.TryGetValue(option, out var perLangDefaultValue)) + { + Assert.Equal(actualDefaultValue.ToString(), perLangDefaultValue.ToString()); + } + else + { + var expectedDefaultValue = option.Definition.DefaultValue?.ToString(); + Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue); + } } private static void VerifyMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) @@ -88,9 +128,9 @@ private static void VerifyMigration(JObject registrationJsonObject, string unifi var migrationType = migrationProperty.Name; if (migrationType is "pass") { - // migration: { - // pass: { - // input: { + // "migration": { + // "pass": { + // "input": { // } // } // } @@ -100,6 +140,20 @@ private static void VerifyMigration(JObject registrationJsonObject, string unifi } else if (migrationType is "enumIntegerToString") { + // "migration": { + // "enumIntegerToString": { + // "input": { + // "store": xxxx, + // "path": yyyy + // }, + // "map": { + // // Enum mappings + // } + // } + // } + // Verify input node and map node + var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.input")!; + VerifyInput(input, option); VerifyEnumIntegerToStringMigration(registrationJsonObject, unifiedSettingPath, option); } else @@ -131,7 +185,7 @@ static void VerifyInput(JToken input, IOption2 option) } } - static void VerifyEnumIntegerToStringMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) + static void VerifyEnumIntegerToStringMigration(JToken registrationJsonObject, string unifiedSettingPath, IOption2 option) { var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.pass.input")!; } diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index e590591bf5e66..0b25ca6d701f9 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -478,7 +478,8 @@ public string GetUnifiedSettingsPath(string language) public static readonly IReadOnlyDictionary UnifiedSettingsStorages = new Dictionary() { - { "dotnet_trigger_completion_on_typing_letters", new UnifiedSettingsStorage() { UnifiedSettingsBasePath = "textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters"} } + { "dotnet_trigger_completion_on_typing_letters", new UnifiedSettingsStorage { UnifiedSettingsBasePath = "textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters"} }, + { "dotnet_snippets_behavior", new UnifiedSettingsStorage { UnifiedSettingsBasePath = "textEditor.%LANGUAGE%.intellisense.snippetsBehavior"} } }; #endregion From 639861872a9939da6d2c385927bee50801b78bec Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 18:06:51 -0700 Subject: [PATCH 0040/1047] Assert enum types --- .../CSharpUnifiedSettingsTests.cs | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index a5dbc6086a86a..2672522a03f0d 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.LanguageServices.Options; using Newtonsoft.Json.Linq; +using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -24,12 +25,19 @@ public class CSharpUnifiedSettingsTests CompletionOptionsStorage.SnippetsBehavior); /// - /// Some options use different default value for C# and VB. The default value in OptionConfig is just a stub value. - /// The real value is set at runtime. But in unified settings we always use the correct value for language. - /// Use this dictionary to indicate that in unit test. + /// The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. + /// But in unified settings we always use the correct value for language. + /// Use this dictionary to indicate that value in unit test. /// private static readonly ImmutableDictionary s_optionsToDefaultValue = ImmutableDictionary.Empty - .Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude.ToString().ToCamelCase()); + .Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude); + + /// + /// The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. + /// Use this dictionary to list all the possible enum values in settings page. + /// + private static readonly ImmutableDictionary> s_enumOptionsToValues = ImmutableDictionary>.Empty + .Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)); [Fact] public async Task IntellisensePageSettingsTest() @@ -53,7 +61,8 @@ public async Task IntellisensePageSettingsTest() VerifyType(registrationJsonObject, unifiedSettingsPath, option); if (option.Type.IsEnum) { - // VerifyEnum(registrationJsonObject, unifiedSettingsPath, option); + // Enum settings contains special setup. + VerifyEnum(registrationJsonObject, unifiedSettingsPath, option); } else { @@ -74,13 +83,23 @@ private static void VerifySettings(JObject registrationJsonObject, string unifie var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; var expectedDefaultValue = option.Definition.DefaultValue?.ToString(); Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue); + VerifyMigration(registrationJsonObject, unifiedSettingPath, option); } private static void VerifyEnum(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { - var enumArray = registrationJsonObject.SelectTokens($"$.properties['{unifiedSettingPath}'].enum"); + var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; + Assert.Equal(actualDefaultValue.ToString(), + s_optionsToDefaultValue.TryGetValue(option, out var perLangDefaultValue) + ? perLangDefaultValue.ToString().ToCamelCase() + : option.Definition.DefaultValue?.ToString()); + var actualEnumValues = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].enum").SelectAsArray(token => token.ToString()); + var expectedEnumValues = s_enumOptionsToValues.TryGetValue(option, out var possibleEnumValues) + ? possibleEnumValues.SelectAsArray(value => value.ToString().ToCamelCase()) + : option.Type.GetEnumValues().Cast().SelectAsArray(value => value.ToString().ToCamelCase()); + AssertEx.Equal(actualEnumValues, expectedEnumValues); } private static void VerifyType(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) From 8e840f0c41343589c7d81edffe76bcf79cb95abd Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 18:12:46 -0700 Subject: [PATCH 0041/1047] Assert enum Migration input --- .../CSharpUnifiedSettingsTests.cs | 91 ++++++++----------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 2672522a03f0d..37905d78fe456 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -100,6 +100,7 @@ private static void VerifyEnum(JObject registrationJsonObject, string unifiedSet ? possibleEnumValues.SelectAsArray(value => value.ToString().ToCamelCase()) : option.Type.GetEnumValues().Cast().SelectAsArray(value => value.ToString().ToCamelCase()); AssertEx.Equal(actualEnumValues, expectedEnumValues); + VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, option); } private static void VerifyType(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) @@ -125,18 +126,27 @@ static string ConvertTypeNameToJsonType(string typeName) }; } - private static void VerifyDefaultValue(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) + private static void VerifyEnumMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { - var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; - if (s_optionsToDefaultValue.TryGetValue(option, out var perLangDefaultValue)) - { - Assert.Equal(actualDefaultValue.ToString(), perLangDefaultValue.ToString()); - } - else - { - var expectedDefaultValue = option.Definition.DefaultValue?.ToString(); - Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue); - } + var actualMigration = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration")!; + var migrationProperty = (JProperty)actualMigration.Children().Single(); + var migrationType = migrationProperty.Name; + Assert.Equal("enumIntegerToString", migrationType); + + // "migration": { + // "enumIntegerToString": { + // "input": { + // "store": xxxx, + // "path": yyyy + // }, + // "map": { + // // Enum mappings + // } + // } + // } + // Verify input node and map node + var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.input")!; + VerifyInput(input, option); } private static void VerifyMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) @@ -157,56 +167,33 @@ private static void VerifyMigration(JObject registrationJsonObject, string unifi var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.pass.input")!; VerifyInput(input, option); } - else if (migrationType is "enumIntegerToString") - { - // "migration": { - // "enumIntegerToString": { - // "input": { - // "store": xxxx, - // "path": yyyy - // }, - // "map": { - // // Enum mappings - // } - // } - // } - // Verify input node and map node - var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.input")!; - VerifyInput(input, option); - VerifyEnumIntegerToStringMigration(registrationJsonObject, unifiedSettingPath, option); - } else { // Need adding more migration types if new type is added throw ExceptionUtilities.UnexpectedValue(migrationType); } + } - // Verify input property under migration - // "input": { - // "store": "xxxx", - // "path": "yyyy" - // } - static void VerifyInput(JToken input, IOption2 option) + // Verify input property under migration + // "input": { + // "store": "xxxx", + // "path": "yyyy" + // } + private static void VerifyInput(JToken input, IOption2 option) + { + var store = input.SelectToken("store")!.ToString(); + var path = input.SelectToken("path")!.ToString(); + var configName = option.Definition.ConfigName; + var visualStudioStorage = VisualStudioOptionStorage.Storages[configName]; + if (visualStudioStorage is VisualStudioOptionStorage.RoamingProfileStorage roamingProfileStorage) { - var store = input.SelectToken("store")!.ToString(); - var path = input.SelectToken("path")!.ToString(); - var configName = option.Definition.ConfigName; - var visualStudioStorage = VisualStudioOptionStorage.Storages[configName]; - if (visualStudioStorage is VisualStudioOptionStorage.RoamingProfileStorage roamingProfileStorage) - { - Assert.Equal("SettingsManager", store); - Assert.Equal(roamingProfileStorage.Key.Replace("%LANGUAGE%", "CSharp"), path); - } - else - { - // Not supported yet - throw ExceptionUtilities.Unreachable(); - } + Assert.Equal("SettingsManager", store); + Assert.Equal(roamingProfileStorage.Key.Replace("%LANGUAGE%", "CSharp"), path); } - - static void VerifyEnumIntegerToStringMigration(JToken registrationJsonObject, string unifiedSettingPath, IOption2 option) + else { - var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.pass.input")!; + // Not supported yet + throw ExceptionUtilities.Unreachable(); } } } From 3cdfc5a5730c5fe9222d731d6814e6907d1df8cb Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 18:54:20 -0700 Subject: [PATCH 0042/1047] Test the IntegerToEnum --- .../CSharpUnifiedSettingsTests.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 37905d78fe456..2bfaed8538683 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.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.IO; using System.Linq; @@ -98,7 +99,7 @@ private static void VerifyEnum(JObject registrationJsonObject, string unifiedSet var actualEnumValues = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].enum").SelectAsArray(token => token.ToString()); var expectedEnumValues = s_enumOptionsToValues.TryGetValue(option, out var possibleEnumValues) ? possibleEnumValues.SelectAsArray(value => value.ToString().ToCamelCase()) - : option.Type.GetEnumValues().Cast().SelectAsArray(value => value.ToString().ToCamelCase()); + : option.Type.GetEnumValues().Cast().SelectAsArray(value => value.ToCamelCase()); AssertEx.Equal(actualEnumValues, expectedEnumValues); VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, option); } @@ -147,6 +148,7 @@ private static void VerifyEnumMigration(JObject registrationJsonObject, string u // Verify input node and map node var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.input")!; VerifyInput(input, option); + VerifyEnumToIntegerMappings(registrationJsonObject, unifiedSettingPath, option); } private static void VerifyMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) @@ -196,5 +198,41 @@ private static void VerifyInput(JToken input, IOption2 option) throw ExceptionUtilities.Unreachable(); } } + + private static void VerifyEnumToIntegerMappings(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) + { + var actualMappings = ((JArray)registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.map")!) + .SelectAsArray(mapping => (mapping["result"]!.ToString(), int.Parse(mapping["match"]!.ToString()))); + + var enumValues = option.Type.GetEnumValues().Cast().ToImmutableDictionary( + enumValue => enumValue.ToString().ToCamelCase(), + enumValue => + { + // If this value is the real default value for the language, we also consider it maps the stub default value + if (s_optionsToDefaultValue.TryGetValue(option, out var realDefaultValue) && realDefaultValue.Equals(enumValue)) + { + return ImmutableArray.Create((int)enumValue, (int)option.DefaultValue!); + } + + return ImmutableArray.Create((int)enumValue); + }); + + foreach (var (result, match) in actualMappings) + { + var acceptableValues = enumValues[result]; + Assert.Contains(match, acceptableValues); + } + + // 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. + if (s_optionsToDefaultValue.TryGetValue(option, out var realDefaultValue)) + { + var indexOfTheRealDefaultMapping = actualMappings.IndexOf((realDefaultValue.ToString().ToCamelCase(), (int)realDefaultValue)); + Assert.NotEqual(-1, indexOfTheRealDefaultMapping); + var indexOfTheDefaultMapping = actualMappings.IndexOf((realDefaultValue.ToString().ToCamelCase(), (int)option.DefaultValue!)); + Assert.NotEqual(-1, indexOfTheDefaultMapping); + Assert.True(indexOfTheRealDefaultMapping < indexOfTheDefaultMapping); + } + } } } From bde9f9e74d2e091286bb32533329ce462928bddd Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Fri, 5 Apr 2024 18:56:50 -0700 Subject: [PATCH 0043/1047] Add commemnts --- .../CSharpUnifiedSettingsTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 2bfaed8538683..715cdb1affb90 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -201,6 +201,29 @@ private static void VerifyInput(JToken input, IOption2 option) private static void VerifyEnumToIntegerMappings(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) { + // 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 + // } + // ] var actualMappings = ((JArray)registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.map")!) .SelectAsArray(mapping => (mapping["result"]!.ToString(), int.Parse(mapping["match"]!.ToString()))); From 4a72fab5c2a3a0a02f662cb8cc892045a8caa90a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 6 Apr 2024 12:30:31 +0000 Subject: [PATCH 0044/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From a8e7b0b683ac24b8690f1150d4abcacfc2d333a9 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 6 Apr 2024 13:22:26 +0000 Subject: [PATCH 0045/1047] Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24204.3 --- eng/Version.Details.xml | 8 ++++---- .../templates-official/steps/component-governance.yml | 2 +- eng/common/templates/steps/component-governance.yml | 2 +- global.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2b46d03cd1241..07b1f7d437360 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - fc2b7849b25c4a21457feb6da5fc7c9806a80976 + 188340e12c0a372b1681ad6a5e72c608021efdba @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - fc2b7849b25c4a21457feb6da5fc7c9806a80976 + 188340e12c0a372b1681ad6a5e72c608021efdba https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/component-governance.yml index 0ecec47b0c917..cbba0596709da 100644 --- a/eng/common/templates-official/steps/component-governance.yml +++ b/eng/common/templates-official/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/component-governance.yml index 0ecec47b0c917..cbba0596709da 100644 --- a/eng/common/templates/steps/component-governance.yml +++ b/eng/common/templates/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/global.json b/global.json index d1af99caa9c33..5aa5a50b34485 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24179.4", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24179.4" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24204.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24204.3" } } From f32e62b00c43e8fde583acfdb0407c0e31b9289e Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 7 Apr 2024 12:29:06 +0000 Subject: [PATCH 0046/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From a3f49983006077ac60363f6d0171ad8f2f2711bb Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 7 Apr 2024 13:09:21 +0000 Subject: [PATCH 0047/1047] Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24204.3 From 206673376e836a6b6d548fc54b74f8cf9197268c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 7 Apr 2024 13:30:17 -0700 Subject: [PATCH 0048/1047] Use string syntax attribute to highlight tests --- ...tConcatenationToInterpolatedStringTests.cs | 121 +++++++----------- .../CSharpCodeRefactoringVerifier`1.cs | 15 ++- .../Utilities/StringSyntaxAttribute.cs | 17 +++ 3 files changed, 72 insertions(+), 81 deletions(-) create mode 100644 src/Features/DiagnosticsTestUtilities/Utilities/StringSyntaxAttribute.cs diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs index f691d31a81708..ce584ea75dd6a 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")] @@ -1035,38 +1007,31 @@ void M() """); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40413")] - public async Task TestConcatenationWithConstMember() + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/40413")] + [InlineData(LanguageVersion.CSharp9)] + [InlineData(LanguageVersion.Preview)] + public async Task TestConcatenationWithConstMember( + LanguageVersion languageVersion) { - var code = """ + await new VerifyCS.Test + { + LanguageVersion = languageVersion, + TestCode = """ class C { const string Hello = "Hello"; const string World = "World"; const string Message = Hello + " " + [||]World; } - """; - var fixedCode = """ + """, + 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(); - - await new VerifyCS.Test - { - LanguageVersion = LanguageVersion.Preview, - TestCode = code, - FixedCode = fixedCode, + """, }.RunAsync(); } 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/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 From ac08d3c891de41a6700bebf083498eebe8fa76cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 7 Apr 2024 13:33:20 -0700 Subject: [PATCH 0049/1047] link file --- .../Microsoft.CodeAnalysis.CodeStyle.UnitTestUtilities.csproj | 1 + 1 file changed, 1 insertion(+) 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 @@ + From ac85d51307ac90046cba2392e7491905f7782c09 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 7 Apr 2024 14:27:48 -0700 Subject: [PATCH 0050/1047] fix --- ...tConcatenationToInterpolatedStringTests.cs | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs index ce584ea75dd6a..6a59132c0fd36 100644 --- a/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs +++ b/src/Features/CSharpTest/ConvertToInterpolatedString/ConvertConcatenationToInterpolatedStringTests.cs @@ -1007,31 +1007,48 @@ void M() """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/40413")] - [InlineData(LanguageVersion.CSharp9)] - [InlineData(LanguageVersion.Preview)] - public async Task TestConcatenationWithConstMember( - LanguageVersion languageVersion) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40413")] + public async Task TestConcatenationWithConstMemberCSharp9() { - await new VerifyCS.Test - { - LanguageVersion = languageVersion, - TestCode = """ + // lang=c#-test + var code = """ 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 = """ + 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}"; - } - """, + class C + { + const string Hello = "Hello"; + const string World = "World"; + const string Message = $"{Hello} {World}"; + } + """, }.RunAsync(); } From 97d9f1ccaf2724fb1a53a6f530118efc01e79a3a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 8 Apr 2024 12:22:31 +0000 Subject: [PATCH 0051/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From b4d4e219b147577a1fabd9ea81338144b4d9b6da Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 8 Apr 2024 13:00:36 +0000 Subject: [PATCH 0052/1047] Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24204.3 From 2fd9319afc2d80488084d611bdeeaba67e176bed Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 10:32:47 -0700 Subject: [PATCH 0053/1047] Add snippet_behavior --- .../Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs | 2 +- .../Core/Def/Options/VisualStudioOptionStorage.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 715cdb1affb90..752845f2064a6 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.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.IO; using System.Linq; @@ -23,6 +22,7 @@ public class CSharpUnifiedSettingsTests { private static readonly ImmutableArray s_onboardedOptions = ImmutableArray.Create( CompletionOptionsStorage.TriggerOnTypingLetters, + CompletionOptionsStorage.TriggerOnDeletion, CompletionOptionsStorage.SnippetsBehavior); /// diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 0b25ca6d701f9..beaa862378199 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -440,7 +440,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti #region UnifiedSettings - internal sealed class UnifiedSettingsStorage : VisualStudioOptionStorage + internal sealed class UnifiedSettingsStorage(string unifiedSettingsBasePath) : VisualStudioOptionStorage { private const string LanguagePlaceholder = "%LANGUAGE%"; @@ -457,7 +457,7 @@ internal sealed class UnifiedSettingsStorage : VisualStudioOptionStorage /// /// Unified settings base path, might contains %LANGAUGE% if it maps to two per-language different setting. /// - public string UnifiedSettingsBasePath { get; init; } + public string UnifiedSettingsBasePath { get; init; } = unifiedSettingsBasePath; public string GetUnifiedSettingsPath(string language) { @@ -478,8 +478,8 @@ public string GetUnifiedSettingsPath(string language) public static readonly IReadOnlyDictionary UnifiedSettingsStorages = new Dictionary() { - { "dotnet_trigger_completion_on_typing_letters", new UnifiedSettingsStorage { UnifiedSettingsBasePath = "textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters"} }, - { "dotnet_snippets_behavior", new UnifiedSettingsStorage { UnifiedSettingsBasePath = "textEditor.%LANGUAGE%.intellisense.snippetsBehavior"} } + { "dotnet_trigger_completion_on_typing_letters", new UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters") }, + { "dotnet_snippets_behavior", new UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior") } }; #endregion From 31bcf3d2e25640415745f86fe3f3c4c280953c78 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 11:58:15 -0700 Subject: [PATCH 0054/1047] Change to VB --- ...o.LanguageServices.CSharp.UnitTests.csproj | 3 - .../CSharpUnifiedSettingsTests.cs | 7 +- .../Def/Options/VisualStudioOptionStorage.cs | 5 - ...alStudio.LanguageServices.UnitTests.vbproj | 3 + .../CSharpUnifiedSettingTests.vb | 9 ++ .../UnifiedSettings/UnifiedSettingsTests.vb | 146 ++++++++++++++++++ .../UnifiedSettingsTests_Storage.vb | 68 ++++++++ 7 files changed, 228 insertions(+), 13 deletions(-) create mode 100644 src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb create mode 100644 src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb create mode 100644 src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb 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 4db7c4fd916a8..9814d14aa5285 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -77,9 +77,6 @@ PreserveNewest - - - diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 752845f2064a6..7630ae366de68 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -1,7 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .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 System.Linq; @@ -18,7 +15,7 @@ namespace Roslyn.VisualStudio.CSharp.UnitTests.UnifiedSettings { - public class CSharpUnifiedSettingsTests + public CSharpUnifiedSettingsTests { private static readonly ImmutableArray s_onboardedOptions = ImmutableArray.Create( CompletionOptionsStorage.TriggerOnTypingLetters, diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index beaa862378199..2853d686e8558 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -476,11 +476,6 @@ public string GetUnifiedSettingsPath(string language) } } - public static readonly IReadOnlyDictionary UnifiedSettingsStorages = new Dictionary() - { - { "dotnet_trigger_completion_on_typing_letters", new UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters") }, - { "dotnet_snippets_behavior", new UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior") } - }; #endregion } diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 7e8b130d74ac7..463bfffc0a7af 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -59,6 +59,9 @@ + + + diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb new file mode 100644 index 0000000000000..cfc30a3bdf934 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb @@ -0,0 +1,9 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings + Public Class CSharpUnifiedSettingTests + + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb new file mode 100644 index 0000000000000..cc3a022b340ad --- /dev/null +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -0,0 +1,146 @@ +' Licensed to the .NET Foundation under one or more 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.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 Class UnifiedSettingsTests + + + Public Async Function IntellisensePageSettingsTest() As Task + Dim x = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceNames() + Dim registrationFileStream = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("csharpSettings.registration.json") + Using reader As New StreamReader(registrationFileStream) + Dim registrationFile = Await reader.ReadToEndAsync().ConfigureAwait(False) + Dim registrationJsonObject = JObject.Parse(registrationFile, New JsonLoadSettings With {.CommentHandling = CommentHandling.Ignore}) + + Dim categoriesTitle = registrationJsonObject.SelectToken("$.categories('textEditor.csharp').title") + Assert.Equal("C#", categoriesTitle.ToString()) + Dim optionPageId = registrationJsonObject.SelectToken("$.categories('textEditor.csharp.intellisense').legacyOptionPageId") + Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId.ToString()) + + For Each onboardedOption In s_onboardedOptions + Dim optionName = onboardedOption.Definition.ConfigName + Dim settingStorage As UnifiedSettingsStorage = Nothing + If s_unifiedSettingsStorage.TryGetValue(optionName, settingStorage) Then + Dim unifiedSettingsPath = settingStorage.GetUnifiedSettingsPath(LanguageNames.CSharp) + VerifyType(registrationJsonObject, unifiedSettingsPath, onboardedOption) + If onboardedOption.Type.IsEnum Then + ' Enum settings contains special setup. + VerifyEnum(registrationJsonObject, unifiedSettingsPath, onboardedOption) + Else + VerifySettings(registrationJsonObject, unifiedSettingsPath, onboardedOption) + End If + Else + ' Can't find the option in the storage dictionary + Throw ExceptionUtilities.UnexpectedValue(optionName) + End If + Next + End Using + End Function + + Private Shared Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + ' Verify default value + Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") + Dim expectedDefaultValue = [option].Definition.DefaultValue?.ToString() + Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue) + + VerifyMigration(registrationJsonObject, unifiedSettingPath, [option]) + End Sub + + Private Shared Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") + Dim perLangDefaultValue As Object = Nothing + Assert.Equal(actualDefaultValue.ToString(), + If(s_optionsToDefaultValue.TryGetValue([option], perLangDefaultValue), + perLangDefaultValue.ToString().ToCamelCase(), + [option].Definition.DefaultValue?.ToString())) + + Dim actualEnumValues = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').enum").SelectAsArray(Function(token) token.ToString()) + Dim possibleEnumValues As ImmutableArray(Of Object) = ImmutableArray(Of Object).Empty + Dim expectedEnumValues = If(s_enumOptionsToValues.TryGetValue([option], possibleEnumValues), + possibleEnumValues.SelectAsArray(Function(value) value.ToString().ToCamelCase()), + [option].Type.GetEnumValues().Cast(Of String).SelectAsArray(Function(value) value.ToCamelCase())) + AssertEx.Equal(actualEnumValues, expectedEnumValues) + VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, [option]) + 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.Name) + Assert.Equal(expectedTypeName, actualType.ToString()) + End If + End Sub + + Private Shared Function ConvertTypeNameToJsonType(TypeName As String) As String + Select Case TypeName + Case "Boolean" + Return "boolean" + Case Else + Return TypeName + End Select + End Function + + Private Shared Sub VerifyEnumMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + 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]) + VerifyEnumToIntegerMappings(registrationJsonObject, unifiedSettingPath, [option]) + End Sub + + Private Shared Sub VerifyMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + 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]) + 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) + 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%", "CSharp"), path) + Else + ' Not supported yet + Throw ExceptionUtilities.Unreachable + End If + End Sub + + Private Shared Sub VerifyEnumToIntegerMappings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + ' Mapping verification omitted for brevity + End Sub + End Class +End Namespace diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb new file mode 100644 index 0000000000000..d0ef2681b7544 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb @@ -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. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Completion +Imports Microsoft.CodeAnalysis.Options + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings + Partial Public Class UnifiedSettingsTests + Private Shared ReadOnly s_onboardedOptions As ImmutableArray(Of IOption2) = ImmutableArray.Create(Of IOption2)( + CompletionOptionsStorage.TriggerOnTypingLetters, + CompletionOptionsStorage.TriggerOnDeletion, + CompletionOptionsStorage.SnippetsBehavior) + + ' Summary: + ' The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. + ' But in unified settings we always use the correct value for language. + ' Use this dictionary to indicate that value in unit test. + Private Shared ReadOnly s_optionsToDefaultValue As ImmutableDictionary(Of IOption2, Object) = ImmutableDictionary(Of IOption2, Object).Empty. + Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude) + + ' Summary: + ' The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. + ' Use this dictionary to list all the possible enum values in settings page. + Private Shared ReadOnly s_enumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) = ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)).Empty. + Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(Of Object)(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)) + + 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_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters")} + } + + 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 From e2133816607a041045c4186c52f28966c962b592 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 14:33:43 -0700 Subject: [PATCH 0055/1047] Convert EnumToInteger --- .../UnifiedSettings/UnifiedSettingsTests.vb | 87 +++++++++++++++---- .../UnifiedSettingsTests_Storage.vb | 7 +- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb index cc3a022b340ad..27b7eb37a0bfd 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -5,6 +5,7 @@ Imports System.Collections.Immutable Imports System.IO Imports System.Reflection +Imports Castle.DynamicProxy.Internal Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Options Imports Microsoft.VisualStudio.LanguageServices.Options @@ -18,7 +19,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Public Async Function IntellisensePageSettingsTest() As Task - Dim x = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceNames() Dim registrationFileStream = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("csharpSettings.registration.json") Using reader As New StreamReader(registrationFileStream) Dim registrationFile = Await reader.ReadToEndAsync().ConfigureAwait(False) @@ -52,8 +52,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Private Shared Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) ' Verify default value Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") - Dim expectedDefaultValue = [option].Definition.DefaultValue?.ToString() - Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue) + Dim perLangDefaultValue As Object = Nothing + Assert.Equal(If(s_optionsToDefaultValue.TryGetValue([option], perLangDefaultValue), + perLangDefaultValue.ToString().ToCamelCase(), + [option].Definition.DefaultValue?.ToString().ToCamelCase()), actualDefaultValue.ToString().ToCamelCase()) VerifyMigration(registrationJsonObject, unifiedSettingPath, [option]) End Sub @@ -61,10 +63,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Private Shared Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") Dim perLangDefaultValue As Object = Nothing - Assert.Equal(actualDefaultValue.ToString(), - If(s_optionsToDefaultValue.TryGetValue([option], perLangDefaultValue), + Assert.Equal(If(s_optionsToDefaultValue.TryGetValue([option], perLangDefaultValue), perLangDefaultValue.ToString().ToCamelCase(), - [option].Definition.DefaultValue?.ToString())) + [option].Definition.DefaultValue?.ToString()), actualDefaultValue.ToString().ToCamelCase()) Dim actualEnumValues = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').enum").SelectAsArray(Function(token) token.ToString()) Dim possibleEnumValues As ImmutableArray(Of Object) = ImmutableArray(Of Object).Empty @@ -82,18 +83,21 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Enum is string in json Assert.Equal("string", actualType.ToString()) Else - Dim expectedTypeName = ConvertTypeNameToJsonType([option].Definition.Type.Name) + Dim expectedTypeName = ConvertTypeNameToJsonType([option].Definition.Type) Assert.Equal(expectedTypeName, actualType.ToString()) End If End Sub - Private Shared Function ConvertTypeNameToJsonType(TypeName As String) As String - Select Case TypeName - Case "Boolean" - Return "boolean" - Case Else - Return TypeName - End Select + 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 Shared Sub VerifyEnumMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) @@ -140,7 +144,60 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End Sub Private Shared Sub VerifyEnumToIntegerMappings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) - ' Mapping verification omitted for brevity + ' 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 As Object = Nothing + If s_optionsToDefaultValue.TryGetValue([option], actualDefaultValue) AndAlso actualDefaultValue.Equals(enumValue) Then + 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 As Object = Nothing + If s_optionsToDefaultValue.TryGetValue([option], realDefaultValue) Then + 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 If End Sub End Class End Namespace diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb index d0ef2681b7544..1f406eb29f4de 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb @@ -19,7 +19,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' But in unified settings we always use the correct value for language. ' Use this dictionary to indicate that value in unit test. Private Shared ReadOnly s_optionsToDefaultValue As ImmutableDictionary(Of IOption2, Object) = ImmutableDictionary(Of IOption2, Object).Empty. - Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude) + Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude). + Add(CompletionOptionsStorage.TriggerOnDeletion, False) ' Summary: ' The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. @@ -29,8 +30,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings 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_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters")} + {"dotnet_trigger_completion_on_deletion", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnDeletion")}, + {"dotnet_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior")} } Friend NotInheritable Class UnifiedSettingsStorage From 755c17cd44e3372c61f19269b8bb0ce4cbe71f74 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 14:34:18 -0700 Subject: [PATCH 0056/1047] Clean file --- .../CSharpUnifiedSettingsTests.cs | 258 ------------------ 1 file changed, 258 deletions(-) delete mode 100644 src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs deleted file mode 100644 index 7630ae366de68..0000000000000 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ /dev/null @@ -1,258 +0,0 @@ - -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.Options; -using Microsoft.VisualStudio.LanguageServices.Options; -using Newtonsoft.Json.Linq; -using Roslyn.Test.Utilities; -using Roslyn.Utilities; -using Xunit; - -namespace Roslyn.VisualStudio.CSharp.UnitTests.UnifiedSettings -{ - public CSharpUnifiedSettingsTests - { - private static readonly ImmutableArray s_onboardedOptions = ImmutableArray.Create( - CompletionOptionsStorage.TriggerOnTypingLetters, - CompletionOptionsStorage.TriggerOnDeletion, - CompletionOptionsStorage.SnippetsBehavior); - - /// - /// The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. - /// But in unified settings we always use the correct value for language. - /// Use this dictionary to indicate that value in unit test. - /// - private static readonly ImmutableDictionary s_optionsToDefaultValue = ImmutableDictionary.Empty - .Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude); - - /// - /// The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. - /// Use this dictionary to list all the possible enum values in settings page. - /// - private static readonly ImmutableDictionary> s_enumOptionsToValues = ImmutableDictionary>.Empty - .Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)); - - [Fact] - public async Task IntellisensePageSettingsTest() - { - 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#", categoriesTitle.ToString()); - var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId")!; - Assert.Equal(Microsoft.VisualStudio.LanguageServices.Guids.CSharpOptionPageIntelliSenseIdString, optionPageId.ToString()); - - foreach (var option in s_onboardedOptions) - { - var optionName = option.Definition.ConfigName; - if (VisualStudioOptionStorage.UnifiedSettingsStorages.TryGetValue(optionName, out var unifiedSettingsStorage)) - { - var unifiedSettingsPath = unifiedSettingsStorage.GetUnifiedSettingsPath(LanguageNames.CSharp); - VerifyType(registrationJsonObject, unifiedSettingsPath, option); - if (option.Type.IsEnum) - { - // Enum settings contains special setup. - VerifyEnum(registrationJsonObject, unifiedSettingsPath, option); - } - else - { - VerifySettings(registrationJsonObject, unifiedSettingsPath, option); - } - } - else - { - // Can't find the option in the storage dictionary - throw ExceptionUtilities.UnexpectedValue(optionName); - } - } - } - - private static void VerifySettings(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) - { - // Verify default value - var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; - var expectedDefaultValue = option.Definition.DefaultValue?.ToString(); - Assert.Equal(actualDefaultValue.ToString(), expectedDefaultValue); - - VerifyMigration(registrationJsonObject, unifiedSettingPath, option); - } - - private static void VerifyEnum(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) - { - var actualDefaultValue = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].default")!; - Assert.Equal(actualDefaultValue.ToString(), - s_optionsToDefaultValue.TryGetValue(option, out var perLangDefaultValue) - ? perLangDefaultValue.ToString().ToCamelCase() - : option.Definition.DefaultValue?.ToString()); - - var actualEnumValues = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].enum").SelectAsArray(token => token.ToString()); - var expectedEnumValues = s_enumOptionsToValues.TryGetValue(option, out var possibleEnumValues) - ? possibleEnumValues.SelectAsArray(value => value.ToString().ToCamelCase()) - : option.Type.GetEnumValues().Cast().SelectAsArray(value => value.ToCamelCase()); - AssertEx.Equal(actualEnumValues, expectedEnumValues); - VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, option); - } - - private static void VerifyType(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) - { - var actualType = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].type")!; - var expectedType = option.Definition.Type; - if (expectedType.IsEnum) - { - // Enum is string in json - Assert.Equal("string", actualType.ToString()); - } - else - { - var expectedTypeName = ConvertTypeNameToJsonType(option.Definition.Type.Name); - Assert.Equal(expectedTypeName, actualType.ToString()); - } - - static string ConvertTypeNameToJsonType(string typeName) - => typeName switch - { - "Boolean" => "boolean", - _ => typeName - }; - } - - private static void VerifyEnumMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) - { - var actualMigration = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration")!; - var migrationProperty = (JProperty)actualMigration.Children().Single(); - var migrationType = migrationProperty.Name; - Assert.Equal("enumIntegerToString", migrationType); - - // "migration": { - // "enumIntegerToString": { - // "input": { - // "store": xxxx, - // "path": yyyy - // }, - // "map": { - // // Enum mappings - // } - // } - // } - // Verify input node and map node - var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.input")!; - VerifyInput(input, option); - VerifyEnumToIntegerMappings(registrationJsonObject, unifiedSettingPath, option); - } - - private static void VerifyMigration(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) - { - var actualMigration = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration")!; - // Get the single property under migration - var migrationProperty = (JProperty)actualMigration.Children().Single(); - var migrationType = migrationProperty.Name; - if (migrationType is "pass") - { - // "migration": { - // "pass": { - // "input": { - // } - // } - // } - // Verify input node - var input = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.pass.input")!; - VerifyInput(input, option); - } - else - { - // Need adding more migration types if new type is added - throw ExceptionUtilities.UnexpectedValue(migrationType); - } - } - - // Verify input property under migration - // "input": { - // "store": "xxxx", - // "path": "yyyy" - // } - private static void VerifyInput(JToken input, IOption2 option) - { - var store = input.SelectToken("store")!.ToString(); - var path = input.SelectToken("path")!.ToString(); - var configName = option.Definition.ConfigName; - var visualStudioStorage = VisualStudioOptionStorage.Storages[configName]; - if (visualStudioStorage is VisualStudioOptionStorage.RoamingProfileStorage roamingProfileStorage) - { - Assert.Equal("SettingsManager", store); - Assert.Equal(roamingProfileStorage.Key.Replace("%LANGUAGE%", "CSharp"), path); - } - else - { - // Not supported yet - throw ExceptionUtilities.Unreachable(); - } - } - - private static void VerifyEnumToIntegerMappings(JObject registrationJsonObject, string unifiedSettingPath, IOption2 option) - { - // 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 - // } - // ] - var actualMappings = ((JArray)registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].migration.enumIntegerToString.map")!) - .SelectAsArray(mapping => (mapping["result"]!.ToString(), int.Parse(mapping["match"]!.ToString()))); - - var enumValues = option.Type.GetEnumValues().Cast().ToImmutableDictionary( - enumValue => enumValue.ToString().ToCamelCase(), - enumValue => - { - // If this value is the real default value for the language, we also consider it maps the stub default value - if (s_optionsToDefaultValue.TryGetValue(option, out var realDefaultValue) && realDefaultValue.Equals(enumValue)) - { - return ImmutableArray.Create((int)enumValue, (int)option.DefaultValue!); - } - - return ImmutableArray.Create((int)enumValue); - }); - - foreach (var (result, match) in actualMappings) - { - var acceptableValues = enumValues[result]; - Assert.Contains(match, acceptableValues); - } - - // 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. - if (s_optionsToDefaultValue.TryGetValue(option, out var realDefaultValue)) - { - var indexOfTheRealDefaultMapping = actualMappings.IndexOf((realDefaultValue.ToString().ToCamelCase(), (int)realDefaultValue)); - Assert.NotEqual(-1, indexOfTheRealDefaultMapping); - var indexOfTheDefaultMapping = actualMappings.IndexOf((realDefaultValue.ToString().ToCamelCase(), (int)option.DefaultValue!)); - Assert.NotEqual(-1, indexOfTheDefaultMapping); - Assert.True(indexOfTheRealDefaultMapping < indexOfTheDefaultMapping); - } - } - } -} From aeb62586be8e2c4f293023d3fcb7d8cd3ea427e4 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 14:45:15 -0700 Subject: [PATCH 0057/1047] Add options --- .../Test/UnifiedSettings/CSharpUnifiedSettingTests.vb | 9 --------- .../Core/Test/UnifiedSettings/UnifiedSettingsTests.vb | 4 +--- .../Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb | 8 +++++++- 3 files changed, 8 insertions(+), 13 deletions(-) delete mode 100644 src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb deleted file mode 100644 index cfc30a3bdf934..0000000000000 --- a/src/VisualStudio/Core/Test/UnifiedSettings/CSharpUnifiedSettingTests.vb +++ /dev/null @@ -1,9 +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.VisualStudio.LanguageServices.UnitTests.UnifiedSettings - Public Class CSharpUnifiedSettingTests - - End Class -End Namespace diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb index 27b7eb37a0bfd..53573212948dd 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -5,7 +5,6 @@ Imports System.Collections.Immutable Imports System.IO Imports System.Reflection -Imports Castle.DynamicProxy.Internal Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Options Imports Microsoft.VisualStudio.LanguageServices.Options @@ -16,9 +15,8 @@ Imports Roslyn.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Partial Public Class UnifiedSettingsTests - - Public Async Function IntellisensePageSettingsTest() As Task + Public Async Function CSharpIntellisensePageSettingsTest() As Task Dim registrationFileStream = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("csharpSettings.registration.json") Using reader As New StreamReader(registrationFileStream) Dim registrationFile = Await reader.ReadToEndAsync().ConfigureAwait(False) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb index 1f406eb29f4de..216cdfccb61be 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb @@ -12,6 +12,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Private Shared ReadOnly s_onboardedOptions As ImmutableArray(Of IOption2) = ImmutableArray.Create(Of IOption2)( CompletionOptionsStorage.TriggerOnTypingLetters, CompletionOptionsStorage.TriggerOnDeletion, + CompletionOptionsStorage.TriggerInArgumentLists, + CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, + CompletionViewOptionsStorage.ShowCompletionItemFilters, CompletionOptionsStorage.SnippetsBehavior) ' Summary: @@ -31,7 +34,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings 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_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior")} + {"dotnet_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior")}, + {"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")} } Friend NotInheritable Class UnifiedSettingsStorage From 92d08dc511252c74e4848701ea470eb37bde0067 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 15:05:40 -0700 Subject: [PATCH 0058/1047] Add comment --- .../Core/Test/UnifiedSettings/UnifiedSettingsTests.vb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb index 53573212948dd..55a924d3ea0a4 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -75,7 +75,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End Sub Private Shared Sub VerifyType(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) - Dim actualType = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').type") + Dim actualType = registrationJsonObject.SelectToken($"$.properties['{unifiedSettingPath}'].type") Dim expectedType = [option].Definition.Type If expectedType.IsEnum Then ' Enum is string in json @@ -172,6 +172,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings elementSelector:=Function(enumValue) Dim actualDefaultValue As Object = Nothing If s_optionsToDefaultValue.TryGetValue([option], actualDefaultValue) AndAlso 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 From b83be8913f3250f5700f1298d6dcf884c8b08e37 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 16:08:57 -0700 Subject: [PATCH 0059/1047] Refactor --- ...o.LanguageServices.CSharp.UnitTests.csproj | 1 + .../CSharpUnifiedSettingsTests.cs | 31 ++++++++++++++++ .../UnifiedSettings/UnifiedSettingsTests.vb | 35 ++++++++++++------- ...edSettingsTests_UnifiedSettingsStorage.vb} | 21 ----------- 4 files changed, 55 insertions(+), 33 deletions(-) create mode 100644 src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs rename src/VisualStudio/Core/Test/UnifiedSettings/{UnifiedSettingsTests_Storage.vb => UnifiedSettingsTests_UnifiedSettingsStorage.vb} (63%) 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..55c8641655cd9 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -38,6 +38,7 @@ + diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs new file mode 100644 index 0000000000000..94b3f65aef13a --- /dev/null +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.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.Immutable; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings; + +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, + CompletionOptionsStorage.SnippetsBehavior + ); + + internal override ImmutableDictionary OptionsToDefaultValue => ImmutableDictionary.Empty. + Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude). + Add(CompletionOptionsStorage.TriggerOnDeletion, false); + + internal override ImmutableDictionary> EnumOptionsToValues => ImmutableDictionary>.Empty. + Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create( + SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)); + } +} diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb index 55a924d3ea0a4..7f6f00aa4f421 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -14,7 +14,18 @@ Imports Roslyn.Test.Utilities Imports Roslyn.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings - Partial Public Class UnifiedSettingsTests + Partial Public MustInherit Class UnifiedSettingsTests + Friend MustOverride ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) + + ' The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. + ' But in unified settings we always use the correct value for language. + ' Use this dictionary to indicate that value in unit test. + Friend MustOverride ReadOnly Property OptionsToDefaultValue As ImmutableDictionary(Of IOption2, Object) + + ' The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. + ' Use this dictionary to list all the possible enum values in settings page. + Friend MustOverride ReadOnly Property EnumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) + Public Async Function CSharpIntellisensePageSettingsTest() As Task Dim registrationFileStream = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("csharpSettings.registration.json") @@ -27,7 +38,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Dim optionPageId = registrationJsonObject.SelectToken("$.categories('textEditor.csharp.intellisense').legacyOptionPageId") Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId.ToString()) - For Each onboardedOption In s_onboardedOptions + For Each onboardedOption In OnboardedOptions Dim optionName = onboardedOption.Definition.ConfigName Dim settingStorage As UnifiedSettingsStorage = Nothing If s_unifiedSettingsStorage.TryGetValue(optionName, settingStorage) Then @@ -47,27 +58,27 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End Using End Function - Private Shared Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Private Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) ' Verify default value Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") Dim perLangDefaultValue As Object = Nothing - Assert.Equal(If(s_optionsToDefaultValue.TryGetValue([option], perLangDefaultValue), + Assert.Equal(If(OptionsToDefaultValue.TryGetValue([option], perLangDefaultValue), perLangDefaultValue.ToString().ToCamelCase(), [option].Definition.DefaultValue?.ToString().ToCamelCase()), actualDefaultValue.ToString().ToCamelCase()) VerifyMigration(registrationJsonObject, unifiedSettingPath, [option]) End Sub - Private Shared Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Private Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") Dim perLangDefaultValue As Object = Nothing - Assert.Equal(If(s_optionsToDefaultValue.TryGetValue([option], perLangDefaultValue), + Assert.Equal(If(OptionsToDefaultValue.TryGetValue([option], perLangDefaultValue), perLangDefaultValue.ToString().ToCamelCase(), [option].Definition.DefaultValue?.ToString()), actualDefaultValue.ToString().ToCamelCase()) Dim actualEnumValues = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').enum").SelectAsArray(Function(token) token.ToString()) Dim possibleEnumValues As ImmutableArray(Of Object) = ImmutableArray(Of Object).Empty - Dim expectedEnumValues = If(s_enumOptionsToValues.TryGetValue([option], possibleEnumValues), + Dim expectedEnumValues = If(EnumOptionsToValues.TryGetValue([option], possibleEnumValues), possibleEnumValues.SelectAsArray(Function(value) value.ToString().ToCamelCase()), [option].Type.GetEnumValues().Cast(Of String).SelectAsArray(Function(value) value.ToCamelCase())) AssertEx.Equal(actualEnumValues, expectedEnumValues) @@ -98,7 +109,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End If End Function - Private Shared Sub VerifyEnumMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Private Sub VerifyEnumMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) Dim actualMigration = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration") Dim migrationProperty = DirectCast(actualMigration.Children().Single(), JProperty) Dim migrationType = migrationProperty.Name @@ -141,7 +152,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End If End Sub - Private Shared Sub VerifyEnumToIntegerMappings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Private Sub VerifyEnumToIntegerMappings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) ' Here we are going to verify a structure like this: ' "map": [ ' { @@ -165,13 +176,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' "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 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 As Object = Nothing - If s_optionsToDefaultValue.TryGetValue([option], actualDefaultValue) AndAlso actualDefaultValue.Equals(enumValue) Then + If OptionsToDefaultValue.TryGetValue([option], actualDefaultValue) AndAlso 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. @@ -192,7 +203,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' 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 As Object = Nothing - If s_optionsToDefaultValue.TryGetValue([option], realDefaultValue) Then + If OptionsToDefaultValue.TryGetValue([option], realDefaultValue) Then 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))) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb similarity index 63% rename from src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb rename to src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb index 216cdfccb61be..dc62eb8dcc85f 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_Storage.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb @@ -9,27 +9,6 @@ Imports Microsoft.CodeAnalysis.Options Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Partial Public Class UnifiedSettingsTests - Private Shared ReadOnly s_onboardedOptions As ImmutableArray(Of IOption2) = ImmutableArray.Create(Of IOption2)( - CompletionOptionsStorage.TriggerOnTypingLetters, - CompletionOptionsStorage.TriggerOnDeletion, - CompletionOptionsStorage.TriggerInArgumentLists, - CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, - CompletionViewOptionsStorage.ShowCompletionItemFilters, - CompletionOptionsStorage.SnippetsBehavior) - - ' Summary: - ' The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. - ' But in unified settings we always use the correct value for language. - ' Use this dictionary to indicate that value in unit test. - Private Shared ReadOnly s_optionsToDefaultValue As ImmutableDictionary(Of IOption2, Object) = ImmutableDictionary(Of IOption2, Object).Empty. - Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude). - Add(CompletionOptionsStorage.TriggerOnDeletion, False) - - ' Summary: - ' The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. - ' Use this dictionary to list all the possible enum values in settings page. - Private Shared ReadOnly s_enumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) = ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)).Empty. - Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(Of Object)(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)) Private Shared ReadOnly s_unifiedSettingsStorage As New Dictionary(Of String, UnifiedSettingsStorage)() From { {"dotnet_trigger_completion_on_typing_letters", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.triggerCompletionOnTypingLetters")}, From 73de970b432d0bffed668fb40889c16f8c2f1243 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 16:33:11 -0700 Subject: [PATCH 0060/1047] Onboard more options --- ...Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj | 1 + .../Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs | 10 +++++++--- .../UnifiedSettingsTests_UnifiedSettingsStorage.vb | 9 ++++----- 3 files changed, 12 insertions(+), 8 deletions(-) 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/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 94b3f65aef13a..2364f8e6d7867 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings; @@ -17,15 +18,18 @@ public class CSharpUnifiedSettingsTests : UnifiedSettingsTests CompletionOptionsStorage.TriggerInArgumentLists, CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, CompletionViewOptionsStorage.ShowCompletionItemFilters, - CompletionOptionsStorage.SnippetsBehavior + CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon, + CompletionOptionsStorage.SnippetsBehavior, + CompletionOptionsStorage.EnterKeyBehavior ); internal override ImmutableDictionary OptionsToDefaultValue => ImmutableDictionary.Empty. Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude). + Add(CompletionOptionsStorage.EnterKeyBehavior, EnterKeyRule.Never). Add(CompletionOptionsStorage.TriggerOnDeletion, false); internal override ImmutableDictionary> EnumOptionsToValues => ImmutableDictionary>.Empty. - Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create( - SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)); + Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)). + Add(CompletionOptionsStorage.EnterKeyBehavior, ImmutableArray.Create(EnterKeyRule.Never, EnterKeyRule.AfterFullyTypedWord, EnterKeyRule.Always)); } } diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb index dc62eb8dcc85f..b310e457433b4 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb @@ -2,10 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Completion -Imports Microsoft.CodeAnalysis.Options Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Partial Public Class UnifiedSettingsTests @@ -13,10 +10,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings 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_snippets_behavior", New UnifiedSettingsStorage("textEditor.%LANGUAGE%.intellisense.snippetsBehavior")}, {"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")} + {"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")} } Friend NotInheritable Class UnifiedSettingsStorage From 628ae0c6d38e4564a4937df406b1e813a8810da8 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 16:45:24 -0700 Subject: [PATCH 0061/1047] Onboarded more options --- .../UnifiedSettings/CSharpUnifiedSettingsTests.cs | 11 +++++++++-- .../UnifiedSettingsTests_UnifiedSettingsStorage.vb | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 2364f8e6d7867..c18cc6df0e35a 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -20,13 +20,20 @@ public class CSharpUnifiedSettingsTests : UnifiedSettingsTests CompletionViewOptionsStorage.ShowCompletionItemFilters, CompleteStatementOptionsStorage.AutomaticallyCompleteStatementOnSemicolon, CompletionOptionsStorage.SnippetsBehavior, - CompletionOptionsStorage.EnterKeyBehavior + CompletionOptionsStorage.EnterKeyBehavior, + CompletionOptionsStorage.ShowNameSuggestions, + CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, + CompletionViewOptionsStorage.EnableArgumentCompletionSnippets, + CompletionOptionsStorage.ShowNewSnippetExperienceUserOption ); internal override ImmutableDictionary OptionsToDefaultValue => ImmutableDictionary.Empty. Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude). Add(CompletionOptionsStorage.EnterKeyBehavior, EnterKeyRule.Never). - Add(CompletionOptionsStorage.TriggerOnDeletion, false); + Add(CompletionOptionsStorage.TriggerOnDeletion, false). + Add(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, true). + Add(CompletionViewOptionsStorage.EnableArgumentCompletionSnippets, false). + Add(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, false); internal override ImmutableDictionary> EnumOptionsToValues => ImmutableDictionary>.Empty. Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)). diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb index b310e457433b4..8c4f13de8f69f 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb @@ -15,7 +15,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings {"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_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 From 1b5d90e5693abef55f2236367bebe5c2d93e1dc8 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 17:02:29 -0700 Subject: [PATCH 0062/1047] Refactor --- ...o.LanguageServices.CSharp.UnitTests.csproj | 3 ++ .../CSharpUnifiedSettingsTests.cs | 20 ++++++++ .../Def/Options/VisualStudioOptionStorage.cs | 41 ---------------- ...alStudio.LanguageServices.UnitTests.vbproj | 3 -- .../UnifiedSettings/UnifiedSettingsTests.vb | 49 +++++++------------ ...iedSettingsTests_UnifiedSettingsStorage.vb | 1 + 6 files changed, 42 insertions(+), 75 deletions(-) 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 55c8641655cd9..4fd945e17cf26 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -81,4 +81,7 @@ + + + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index c18cc6df0e35a..42d7254e512ab 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -3,10 +3,16 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; 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 { @@ -38,5 +44,19 @@ public class CSharpUnifiedSettingsTests : UnifiedSettingsTests internal override ImmutableDictionary> EnumOptionsToValues => ImmutableDictionary>.Empty. Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)). Add(CompletionOptionsStorage.EnterKeyBehavior, ImmutableArray.Create(EnterKeyRule.Never, EnterKeyRule.AfterFullyTypedWord, EnterKeyRule.Always)); + + [Fact] + public async Task IntelliSensePageTests() + { + 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#", categoriesTitle.ToString()); + var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId"); + Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId!.ToString()); + TestIntelliSensePageSettings(registrationJsonObject); + } } } diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 2853d686e8558..04342100e7d9e 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -437,45 +437,4 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_enable_opening_source_generated_files_in_workspace_feature_flag", new FeatureFlagStorage(@"Roslyn.SourceGeneratorsEnableOpeningInWorkspace")}, {"xaml_enable_lsp_intellisense", new FeatureFlagStorage(@"Xaml.EnableLspIntelliSense")}, }; - - #region UnifiedSettings - - internal sealed class UnifiedSettingsStorage(string unifiedSettingsBasePath) : VisualStudioOptionStorage - { - private const string LanguagePlaceholder = "%LANGUAGE%"; - - /// - /// C# name used in Unified Settings path. - /// - private const string csharpKey = "csharp"; - - /// - /// Visual Basic name used in Unified Settings path. - /// - private const string visualBasicKey = "basic"; - - /// - /// Unified settings base path, might contains %LANGAUGE% if it maps to two per-language different setting. - /// - public string UnifiedSettingsBasePath { get; init; } = unifiedSettingsBasePath; - - public string GetUnifiedSettingsPath(string language) - { - if (!UnifiedSettingsBasePath.Contains(LanguagePlaceholder)) - { - return UnifiedSettingsBasePath; - } - - return language switch - { - LanguageNames.CSharp => UnifiedSettingsBasePath.Replace(LanguagePlaceholder, csharpKey), - LanguageNames.VisualBasic => UnifiedSettingsBasePath.Replace(LanguagePlaceholder, visualBasicKey), - // Current we don't expect to handle other languages - _ => throw ExceptionUtilities.UnexpectedValue(language) - }; - } - } - - - #endregion } diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 463bfffc0a7af..7e8b130d74ac7 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -59,9 +59,6 @@ - - - diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb index 7f6f00aa4f421..23809541da74c 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -3,8 +3,6 @@ ' 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.Options Imports Microsoft.VisualStudio.LanguageServices.Options @@ -15,6 +13,7 @@ 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) ' The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. @@ -26,37 +25,25 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Use this dictionary to list all the possible enum values in settings page. Friend MustOverride ReadOnly Property EnumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) - - Public Async Function CSharpIntellisensePageSettingsTest() As Task - Dim registrationFileStream = GetType(UnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("csharpSettings.registration.json") - Using reader As New StreamReader(registrationFileStream) - Dim registrationFile = Await reader.ReadToEndAsync().ConfigureAwait(False) - Dim registrationJsonObject = JObject.Parse(registrationFile, New JsonLoadSettings With {.CommentHandling = CommentHandling.Ignore}) - - Dim categoriesTitle = registrationJsonObject.SelectToken("$.categories('textEditor.csharp').title") - Assert.Equal("C#", categoriesTitle.ToString()) - Dim optionPageId = registrationJsonObject.SelectToken("$.categories('textEditor.csharp.intellisense').legacyOptionPageId") - Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId.ToString()) - - 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(LanguageNames.CSharp) - VerifyType(registrationJsonObject, unifiedSettingsPath, onboardedOption) - If onboardedOption.Type.IsEnum Then - ' Enum settings contains special setup. - VerifyEnum(registrationJsonObject, unifiedSettingsPath, onboardedOption) - Else - VerifySettings(registrationJsonObject, unifiedSettingsPath, onboardedOption) - End If + Protected Sub TestIntelliSensePageSettings(registrationJsonObject As JObject) + 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(LanguageNames.CSharp) + VerifyType(registrationJsonObject, unifiedSettingsPath, onboardedOption) + If onboardedOption.Type.IsEnum Then + ' Enum settings contains special setup. + VerifyEnum(registrationJsonObject, unifiedSettingsPath, onboardedOption) Else - ' Can't find the option in the storage dictionary - Throw ExceptionUtilities.UnexpectedValue(optionName) + VerifySettings(registrationJsonObject, unifiedSettingsPath, onboardedOption) End If - Next - End Using - End Function + Else + ' Can't find the option in the storage dictionary + Throw ExceptionUtilities.UnexpectedValue(optionName) + End If + Next + End Sub Private Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) ' Verify default value diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb index 8c4f13de8f69f..b253bc0db5914 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb @@ -7,6 +7,7 @@ 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")}, From acedf0f84b082bdb6386faa580d6241ef560156d Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 8 Apr 2024 17:09:21 -0700 Subject: [PATCH 0063/1047] Code cleanup --- .../Core/Test/UnifiedSettings/UnifiedSettingsTests.vb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb index 23809541da74c..64c1327499921 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb @@ -16,7 +16,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Onboarded options in Unified Settings registration file Friend MustOverride ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) - ' The default value of some enum options is overridden at runtime. It uses different default value for C# and VB. + ' The default value of some enum options is overridden at runtime. + ' It could be + ' 1: Option has different default value for C# and VB. + ' 2: Option is in experiment, so it is set to null and override to a value. ' But in unified settings we always use the correct value for language. ' Use this dictionary to indicate that value in unit test. Friend MustOverride ReadOnly Property OptionsToDefaultValue As ImmutableDictionary(Of IOption2, Object) @@ -68,7 +71,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Dim expectedEnumValues = If(EnumOptionsToValues.TryGetValue([option], possibleEnumValues), possibleEnumValues.SelectAsArray(Function(value) value.ToString().ToCamelCase()), [option].Type.GetEnumValues().Cast(Of String).SelectAsArray(Function(value) value.ToCamelCase())) - AssertEx.Equal(actualEnumValues, expectedEnumValues) + AssertEx.Equal(expectedEnumValues, actualEnumValues) VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, [option]) End Sub From b30e71d8c285aa5c705776852755c3626b3f0726 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Tue, 9 Apr 2024 12:28:57 +0000 Subject: [PATCH 0064/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240401.1 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520101 From 6540f76d5e16e5887a05bbe2da78ab9bd686d085 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Tue, 9 Apr 2024 11:32:38 -0700 Subject: [PATCH 0065/1047] Move test code to test utilities --- ...oft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj | 1 - .../Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs | 2 +- .../Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj | 5 +++++ .../UnifiedSettings/UnifiedSettingsTests.vb | 0 .../UnifiedSettingsTests_UnifiedSettingsStorage.vb | 0 5 files changed, 6 insertions(+), 2 deletions(-) rename src/VisualStudio/{Core/Test => TestUtilities2}/UnifiedSettings/UnifiedSettingsTests.vb (100%) rename src/VisualStudio/{Core/Test => TestUtilities2}/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb (100%) 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 4fd945e17cf26..a1822e09b908e 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -38,7 +38,6 @@ - diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 42d7254e512ab..b30760de906c1 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -53,7 +53,7 @@ public async Task IntelliSensePageTests() 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#", categoriesTitle.ToString()); + Assert.Equal("C#", actual: categoriesTitle.ToString()); var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId"); Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId!.ToString()); TestIntelliSensePageSettings(registrationJsonObject); diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 7e8b130d74ac7..99ca1244cb062 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -7,6 +7,11 @@ true + + + + + diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb similarity index 100% rename from src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests.vb rename to src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb similarity index 100% rename from src/VisualStudio/Core/Test/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb rename to src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests_UnifiedSettingsStorage.vb From 46d5a7976e91d39120860da9e599fe9059bf35af Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Tue, 9 Apr 2024 12:15:14 -0700 Subject: [PATCH 0066/1047] Assert only Onboarded options live in the page --- .../CSharpUnifiedSettingsTests.cs | 3 +- .../UnifiedSettings/UnifiedSettingsTests.vb | 50 +++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index b30760de906c1..30f603b4a4dc4 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement; using Microsoft.CodeAnalysis.Options; @@ -56,7 +57,7 @@ public async Task IntelliSensePageTests() Assert.Equal("C#", actual: categoriesTitle.ToString()); var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId"); Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId!.ToString()); - TestIntelliSensePageSettings(registrationJsonObject); + TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath: "textEditor.csharp.intellisense", languageName: LanguageNames.CSharp); } } } diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb index 64c1327499921..d6e393ea34c74 100644 --- a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -28,18 +28,29 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Use this dictionary to list all the possible enum values in settings page. Friend MustOverride ReadOnly Property EnumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) - Protected Sub TestIntelliSensePageSettings(registrationJsonObject As JObject) + Protected Sub TestUnifiedSettingsCategory(registrationJsonObject As JObject, categoryBasePath As String, languageName 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(LanguageNames.CSharp) + Dim unifiedSettingsPath = settingStorage.GetUnifiedSettingsPath(languageName) VerifyType(registrationJsonObject, unifiedSettingsPath, onboardedOption) If onboardedOption.Type.IsEnum Then ' Enum settings contains special setup. - VerifyEnum(registrationJsonObject, unifiedSettingsPath, onboardedOption) + VerifyEnum(registrationJsonObject, unifiedSettingsPath, onboardedOption, languageName) Else - VerifySettings(registrationJsonObject, unifiedSettingsPath, onboardedOption) + VerifySettings(registrationJsonObject, unifiedSettingsPath, onboardedOption, languageName) End If Else ' Can't find the option in the storage dictionary @@ -48,7 +59,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Next End Sub - Private Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Private Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) ' Verify default value Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") Dim perLangDefaultValue As Object = Nothing @@ -56,10 +67,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings perLangDefaultValue.ToString().ToCamelCase(), [option].Definition.DefaultValue?.ToString().ToCamelCase()), actualDefaultValue.ToString().ToCamelCase()) - VerifyMigration(registrationJsonObject, unifiedSettingPath, [option]) + VerifyMigration(registrationJsonObject, unifiedSettingPath, [option], languageName) End Sub - Private Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + Private Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") Dim perLangDefaultValue As Object = Nothing Assert.Equal(If(OptionsToDefaultValue.TryGetValue([option], perLangDefaultValue), @@ -72,7 +83,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings possibleEnumValues.SelectAsArray(Function(value) value.ToString().ToCamelCase()), [option].Type.GetEnumValues().Cast(Of String).SelectAsArray(Function(value) value.ToCamelCase())) AssertEx.Equal(expectedEnumValues, actualEnumValues) - VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, [option]) + VerifyEnumMigration(registrationJsonObject, unifiedSettingPath, [option], languageName) End Sub Private Shared Sub VerifyType(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) @@ -99,7 +110,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End If End Function - Private Sub VerifyEnumMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + 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 @@ -107,11 +118,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Verify input node and map node Dim input = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration.enumIntegerToString.input") - VerifyInput(input, [option]) + VerifyInput(input, [option], languageName) VerifyEnumToIntegerMappings(registrationJsonObject, unifiedSettingPath, [option]) End Sub - Private Shared Sub VerifyMigration(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2) + 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) @@ -119,7 +130,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings If migrationType = "pass" Then ' Verify input node Dim input = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').migration.pass.input") - VerifyInput(input, [option]) + VerifyInput(input, [option], languageName) Else ' Need adding more migration types if new type is added Throw ExceptionUtilities.UnexpectedValue(migrationType) @@ -127,7 +138,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End Sub ' Verify input property under migration - Private Shared Sub VerifyInput(input As JToken, [option] As IOption2) + 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 @@ -135,13 +146,24 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings If TypeOf visualStudioStorage Is VisualStudioOptionStorage.RoamingProfileStorage Then Dim roamingProfileStorage = DirectCast(visualStudioStorage, VisualStudioOptionStorage.RoamingProfileStorage) Assert.Equal("SettingsManager", store) - Assert.Equal(roamingProfileStorage.Key.Replace("%LANGUAGE%", "CSharp"), path) + 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": [ From da8d7e0a8578379116f5888e3047909857ad47d3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 19:49:51 -0700 Subject: [PATCH 0067/1047] Add perf logging --- .../Remote/ServiceHub/Host/AssetProvider.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index ad5f1ca91a42e..d28f51e6c1e84 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -22,6 +22,14 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { + private const string s_logFile = @"c:\temp\sync\synclog.txt"; + private static readonly SharedStopwatch s_start = SharedStopwatch.StartNew(); + + static AssetProvider() + { + IOUtilities.PerformIO(() => File.Delete(s_logFile)); + } + private const int PooledChecksumArraySize = 256; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); @@ -240,6 +248,8 @@ public async ValueTask SynchronizeAssetsAsync( { var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); + var stopwatch = SharedStopwatch.StartNew(); + await RequestAssetsAsync( assetPath, missingChecksumsMemory, static ( @@ -254,6 +264,17 @@ await RequestAssetsAsync( }, (this, missingChecksums, callback, arg), cancellationToken).ConfigureAwait(false); + + var time = stopwatch.Elapsed; + var totalTime = s_start.Elapsed; + + IOUtilities.PerformIO(() => + { + lock (this) + { + File.AppendAllText(s_logFile, $"{missingChecksumsCount},{checksums.Count},{time},{totalTime},{typeof(T).Name}\r\n"); + } + }); } } finally From 9829da1343f54bbeabab4f0fde53b0a7780b75db Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 20:08:31 -0700 Subject: [PATCH 0068/1047] sync in strips --- .../Workspace/Solution/StateChecksums.cs | 3 + .../Remote/ServiceHub/Host/AssetProvider.cs | 125 +++++++++++++----- 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 1dcfee3b27bed..0252b61ca7315 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -280,6 +280,9 @@ public async Task FindAsync( if (searchingChecksumsLeft.Remove(projectStateChecksums.CompilationOptions)) result[projectStateChecksums.CompilationOptions] = projectState.CompilationOptions!; + + if (searchingChecksumsLeft.Remove(projectStateChecksums.ParseOptions)) + result[projectStateChecksums.ParseOptions] = projectState.ParseOptions!; } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index d28f51e6c1e84..f2d2162f1ff3d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; @@ -117,16 +118,21 @@ await this.SynchronizeAssetsAsync>( static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); + using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); + // fourth, get all projects and documents in the solution foreach (var (projectChecksum, _) in stateChecksums.Projects) { var projectStateChecksums = (ProjectStateChecksums)checksumToObjects[projectChecksum]; - await SynchronizeProjectAssetsAsync(projectStateChecksums, cancellationToken).ConfigureAwait(false); + allProjectStateChecksums.Add(projectStateChecksums); } + + 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 @@ -137,7 +143,7 @@ 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)) { await SynchronizeProjectAssetsWorkerAsync().ConfigureAwait(false); } @@ -147,41 +153,96 @@ async ValueTask SynchronizeProjectAssetsWorkerAsync() // get children of project checksum objects at once using var _ = PooledHashSet.GetInstance(out var checksums); - checksums.Add(projectChecksums.Info); - checksums.Add(projectChecksums.CompilationOptions); - checksums.Add(projectChecksums.ParseOptions); - AddAll(checksums, projectChecksums.ProjectReferences); - AddAll(checksums, projectChecksums.MetadataReferences); - AddAll(checksums, projectChecksums.AnalyzerReferences); - AddAll(checksums, projectChecksums.Documents.Checksums); - AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); - AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); + { + checksums.Clear(); + foreach (var projectChecksums in allProjectChecksums) + checksums.Add(projectChecksums.Info); - // First synchronize all the top-level info about this project. - await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } - checksums.Clear(); + { + checksums.Clear(); + foreach (var projectChecksums in allProjectChecksums) + checksums.Add(projectChecksums.CompilationOptions); - // Then synchronize the info about all the documents within. - await CollectChecksumChildrenAsync(checksums, projectChecksums.Documents).ConfigureAwait(false); - await CollectChecksumChildrenAsync(checksums, projectChecksums.AdditionalDocuments).ConfigureAwait(false); - await CollectChecksumChildrenAsync(checksums, projectChecksums.AnalyzerConfigDocuments).ConfigureAwait(false); + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } - await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + { + checksums.Clear(); + foreach (var projectChecksums in allProjectChecksums) + checksums.Add(projectChecksums.ParseOptions); + + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } + + { + checksums.Clear(); + foreach (var projectChecksums in allProjectChecksums) + AddAll(checksums, projectChecksums.ProjectReferences); + + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } + + { + checksums.Clear(); + foreach (var projectChecksums in allProjectChecksums) + AddAll(checksums, projectChecksums.MetadataReferences); + + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } - async ValueTask CollectChecksumChildrenAsync(HashSet checksums, ChecksumsAndIds collection) - { - // This GetAssetsAsync call 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 allDocChecksums = await this.GetAssetsAsync( - AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), collection.Checksums, cancellationToken).ConfigureAwait(false); - foreach (var docChecksums in allDocChecksums) { - checksums.Add(docChecksums.Info); - checksums.Add(docChecksums.Text); + checksums.Clear(); + foreach (var projectChecksums in allProjectChecksums) + AddAll(checksums, projectChecksums.AnalyzerReferences); + + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } + + using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); + + foreach (var projectChecksums in allProjectChecksums) + { + allDocumentStateChecksums.Clear(); + checksums.Clear(); + + AddAll(checksums, projectChecksums.Documents.Checksums); + AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); + AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); + + // First synchronize all the top-level info about this project. + + await this.SynchronizeAssetsAsync>( + assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, + static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), + allDocumentStateChecksums, + cancellationToken).ConfigureAwait(false); + + { + checksums.Clear(); + foreach (var docChecksums in allDocumentStateChecksums) + checksums.Add(docChecksums.Info); + + await this.SynchronizeAssetsAsync( + assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } + + { + checksums.Clear(); + foreach (var docChecksums in allDocumentStateChecksums) + checksums.Add(docChecksums.Text); + + await this.SynchronizeAssetsAsync( + assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } } } From 3552d4b32a6bf968b2b3136bd58c4e55f72ca52c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 20:13:34 -0700 Subject: [PATCH 0069/1047] in progress' --- .../Host/RemoteWorkspace.SolutionCreator.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index d9c8cbaaf0a6f..71b2a40a9a17e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -264,16 +264,26 @@ await _assetProvider.GetAssetsAsync( } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); + using var _3 = 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 won't want + + await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); + - await _assetProvider.SynchronizeProjectAssetsAsync(newProjectChecksums, cancellationToken).ConfigureAwait(false); + 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(projectId, newProjectChecksums.Checksum, cancellationToken).ConfigureAwait(false); projectInfos.Add(projectInfo); } From 6377536326718176b2721329fd97a189ac379690 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 20:13:56 -0700 Subject: [PATCH 0070/1047] fix --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 71b2a40a9a17e..f35a49673f193 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -277,7 +277,6 @@ await _assetProvider.GetAssetsAsync( await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); - foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) @@ -305,7 +304,7 @@ await _assetProvider.GetAssetsAsync( } } - using var _3 = ArrayBuilder.GetInstance(out var projectsToRemove); + using var _4 = ArrayBuilder.GetInstance(out var projectsToRemove); // removed project foreach (var (projectId, _) in oldProjectIdToStateChecksums) From d150afd889ef0217cc748e6bf4c8aae9054c5a44 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 20:24:17 -0700 Subject: [PATCH 0071/1047] downstream --- .../Core/Test.Next/Services/AssetProviderTests.cs | 7 ++++++- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index c80be5d4721fe..5824e81d1dcda 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -12,6 +12,7 @@ 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; @@ -137,7 +138,11 @@ 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); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index f35a49673f193..c1c2602dec1c2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -558,7 +558,9 @@ await _assetProvider.GetAssetsAsync 2) { - await _assetProvider.SynchronizeProjectAssetsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + allProjectChecksums.Add(projectChecksums); + await _assetProvider.SynchronizeProjectAssetsAsync(allProjectChecksums, cancellationToken).ConfigureAwait(false); } return await UpdateDocumentsAsync(project, addDocuments, removeDocuments, oldDocumentIdToStateChecksums, newDocumentIdToStateChecksums, cancellationToken).ConfigureAwait(false); From a255fa819827b2f57f3cc70007c7b43fdb182622 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 20:47:46 -0700 Subject: [PATCH 0072/1047] Fetch attributes up front' --- .../Host/RemoteWorkspace.SolutionCreator.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index c1c2602dec1c2..b8a25fbe80c09 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -612,19 +612,27 @@ private async Task UpdateDocumentsAsync( } // changed document + using var _ = PooledHashSet.GetInstance(out var checksums); foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) { - if (!oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) - { - continue; - } + if (oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) + checksums.Add(newDocumentChecksums.Info); + } + + await _assetProvider.GetAssetsAsync( + AssetPath.ProjectAndDocuments(project.Id), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(oldDocumentChecksums.Checksum == newDocumentChecksums.Checksum); + foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) + { + if (oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) + { + Contract.ThrowIfTrue(oldDocumentChecksums.Checksum == newDocumentChecksums.Checksum); - var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); - Contract.ThrowIfNull(document); + var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); + Contract.ThrowIfNull(document); - project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); + project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); + } } return project; From 6d82a4b3e7aa8b855b72dbacd186d43c90737f39 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 9 Apr 2024 21:11:45 -0700 Subject: [PATCH 0073/1047] get info and text together --- .../Remote/ServiceHub/Host/AssetProvider.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index f2d2162f1ff3d..0beae0615e955 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -229,18 +229,12 @@ await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } - - { - checksums.Clear(); - foreach (var docChecksums in allDocumentStateChecksums) checksums.Add(docChecksums.Text); + } - await this.SynchronizeAssetsAsync( + await this.SynchronizeAssetsAsync( assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } } From 3aa8f0e924c08faf8e05c8896d53d9a1a7b5ed5e Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 10 Apr 2024 12:30:55 +0000 Subject: [PATCH 0074/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240409.3 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520903 --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index edc7832c1bc5e..0a4e636de5c53 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,14 +13,14 @@ c0b5d69a1a1513528c77fffff708c7502d57c35c - + https://github.com/dotnet/command-line-api - c96672b8b84c307feb035fed6cbe9db85d5b87d3 + 963d34b1fb712c673bfb198133d7e988182c9ef4 - + https://github.com/dotnet/command-line-api - c96672b8b84c307feb035fed6cbe9db85d5b87d3 + 963d34b1fb712c673bfb198133d7e988182c9ef4 diff --git a/eng/Versions.props b/eng/Versions.props index 240ddbdc9dad9..78eb11bdac83d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,7 +20,7 @@ Versions managed by Arcade (see Versions.Details.xml) --> - 2.0.0-beta4.24201.1 + 2.0.0-beta4.24209.3 8.0.0 8.0.0 8.0.0 From 046d435cc3a2089e7e7a4ac08c44647c0a5980de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 15:05:05 -0700 Subject: [PATCH 0075/1047] sync in parallel --- .../Portable/Workspace/Solution/AssetHint.cs | 2 + .../Workspace/Solution/StateChecksums.cs | 18 +-- .../Remote/ServiceHub/Host/AssetProvider.cs | 139 ++++++++---------- 3 files changed, 70 insertions(+), 89 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs index 634f641fa9948..76f3dd03e497c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs @@ -27,6 +27,8 @@ internal readonly struct AssetPath /// public static readonly AssetPath SolutionAndTopLevelProjectsOnly = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects); + public static readonly AssetPath ProjectOnly = new(AssetPathKind.Projects); + /// /// 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. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 0252b61ca7315..2c6d096818bc0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -272,17 +272,13 @@ public async Task FindAsync( if (projectState.TryGetStateChecksums(out var projectStateChecksums)) { - if (searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) - result[projectStateChecksums.Checksum] = projectStateChecksums; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.Info)) - result[projectStateChecksums.Info] = projectState.Attributes; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.CompilationOptions)) - result[projectStateChecksums.CompilationOptions] = projectState.CompilationOptions!; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.ParseOptions)) - result[projectStateChecksums.ParseOptions] = projectState.ParseOptions!; + await projectStateChecksums.FindAsync( + projectState, + // Only search the project level info of the project we're diving into. Do not go into documents. + AssetPath.ProjectOnly, + searchingChecksumsLeft, + result, + cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 0beae0615e955..4203c4ba660cc 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -31,7 +31,7 @@ static AssetProvider() IOUtilities.PerformIO(() => File.Delete(s_logFile)); } - 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; @@ -150,100 +150,83 @@ public async ValueTask SynchronizeProjectAssetsAsync( async ValueTask SynchronizeProjectAssetsWorkerAsync() { - // get children of project checksum objects at once - using var _ = PooledHashSet.GetInstance(out var checksums); - - { - checksums.Clear(); - foreach (var projectChecksums in allProjectChecksums) - checksums.Add(projectChecksums.Info); - - await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } - - { - checksums.Clear(); - foreach (var projectChecksums in allProjectChecksums) - checksums.Add(projectChecksums.CompilationOptions); - - await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + using var _ = ArrayBuilder.GetInstance(out var tasks); - { - checksums.Clear(); - foreach (var projectChecksums in allProjectChecksums) - checksums.Add(projectChecksums.ParseOptions); - - await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + // Make parallel requests for all the project data across all projects at once. + tasks.Add(SynchronizeProjectAssetAsync(static p => p.Info, cancellationToken)); + tasks.Add(SynchronizeProjectAssetAsync(static p => p.CompilationOptions, cancellationToken)); + tasks.Add(SynchronizeProjectAssetAsync(static p => p.ParseOptions, cancellationToken)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(static p => p.ProjectReferences, cancellationToken)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(static p => p.MetadataReferences, cancellationToken)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(static p => p.AnalyzerReferences, cancellationToken)); - { - checksums.Clear(); - foreach (var projectChecksums in allProjectChecksums) - AddAll(checksums, projectChecksums.ProjectReferences); + // Then sync each project's documents in parallel with each other. + foreach (var projectChecksums in allProjectChecksums) + tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken)); - await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + await Task.WhenAll(tasks).ConfigureAwait(false); + } - { - checksums.Clear(); - foreach (var projectChecksums in allProjectChecksums) - AddAll(checksums, projectChecksums.MetadataReferences); + static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) + { + foreach (var checksum in checksumCollection) + checksums.Add(checksum); + } - await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + async Task SynchronizeProjectAssetAsync(Func getChecksum, CancellationToken cancellationToken) + { + await Task.Yield(); + using var _ = PooledHashSet.GetInstance(out var checksums); - { - checksums.Clear(); - foreach (var projectChecksums in allProjectChecksums) - AddAll(checksums, projectChecksums.AnalyzerReferences); + foreach (var projectChecksums in allProjectChecksums) + checksums.Add(getChecksum(projectChecksums)); - await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } - using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); + async Task SynchronizeProjectAssetCollectionAsync(Func getChecksums, CancellationToken cancellationToken) + { + await Task.Yield(); + using var _ = PooledHashSet.GetInstance(out var checksums); foreach (var projectChecksums in allProjectChecksums) - { - allDocumentStateChecksums.Clear(); - checksums.Clear(); + AddAll(checksums, getChecksums(projectChecksums)); - AddAll(checksums, projectChecksums.Documents.Checksums); - AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); - AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + } - // First synchronize all the top-level info about this project. + async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) + { + await Task.Yield(); + using var _1 = PooledHashSet.GetInstance(out var checksums); - await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, - static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), - allDocumentStateChecksums, - cancellationToken).ConfigureAwait(false); + AddAll(checksums, projectChecksums.Documents.Checksums); + AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); + AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); - { - checksums.Clear(); - foreach (var docChecksums in allDocumentStateChecksums) - { - checksums.Add(docChecksums.Info); - checksums.Add(docChecksums.Text); - } + await this.SynchronizeAssetsAsync( + AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - } + // First, fetch all the DocumentStateChecksums for all the documents in the project. + using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); + await this.SynchronizeAssetsAsync>( + assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, + static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), + allDocumentStateChecksums, + cancellationToken).ConfigureAwait(false); + + // Now go and fetch the info and text for all of those documents. + checksums.Clear(); + foreach (var docChecksums in allDocumentStateChecksums) + { + checksums.Add(docChecksums.Info); + checksums.Add(docChecksums.Text); } - } - static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) - { - foreach (var checksum in checksumCollection) - checksums.Add(checksum); + await this.SynchronizeAssetsAsync( + assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } } From c8b9a0fb136302d4a5b5d6fdb68bd7ea38172b14 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 15:16:32 -0700 Subject: [PATCH 0076/1047] fix --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 4203c4ba660cc..822b1de0d8cb1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -207,7 +207,7 @@ async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksu AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); // First, fetch all the DocumentStateChecksums for all the documents in the project. using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); From d140c85924b80146ebd8ed3f4421f93ef3215d19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 15:17:08 -0700 Subject: [PATCH 0077/1047] delete --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 822b1de0d8cb1..e63ed6d06ac40 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -206,9 +206,6 @@ async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksu AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); - await this.SynchronizeAssetsAsync( - AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - // First, fetch all the DocumentStateChecksums for all the documents in the project. using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); await this.SynchronizeAssetsAsync>( From cbb89bcfbf31a4ba202c816bc25f54916915ded1 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 20 Mar 2024 16:21:27 +1100 Subject: [PATCH 0078/1047] Expose a way for Razor to register capabilities into the AlwaysActivate server --- .../Protocol/LanguageInfoProvider.cs | 5 +- .../ExternalAccess/Razor/Cohost/Constants.cs | 2 + .../ExportCohostLspServiceFactoryAttribute.cs | 12 +++++ ...xportCohostStatelessLspServiceAttribute.cs | 12 +++++ .../IRazorCohostDynamicRegistrationService.cs | 13 +++++ .../RazorCohostClientLanguageServerManager.cs | 27 ++++++++++ ...ohostClientLanguageServerManagerFactory.cs | 20 ------- .../RazorDynamicRegistrationServiceFactory.cs | 54 +++++++++++++++++++ 8 files changed, 124 insertions(+), 21 deletions(-) create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/ExportCohostLspServiceFactoryAttribute.cs create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/ExportCohostStatelessLspServiceAttribute.cs create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManager.cs create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs 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/Tools/ExternalAccess/Razor/Cohost/Constants.cs b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs index 520faed5e9018..1c361a9fc7003 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs @@ -9,6 +9,8 @@ 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; 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..888b7ce6737f7 --- /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, IRazorCohostClientLanguageServerManager razorCohostClientLanguageServerManager, 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..7491f391c156d 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs @@ -4,8 +4,6 @@ using System; using System.Composition; -using System.Threading.Tasks; -using System.Threading; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; @@ -22,22 +20,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/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs new file mode 100644 index 0000000000000..4a94540990fd4 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Newtonsoft.Json; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +[ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] IRazorCohostDynamicRegistrationService? dynamicRegistrationService) : ILspServiceFactory +{ + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + { + var clientLanguageServerManager = lspServices.GetRequiredService(); + + return new RazorDynamicRegistrationService(dynamicRegistrationService, clientLanguageServerManager); + } + + private class RazorDynamicRegistrationService : ILspService, IOnInitialized + { + private readonly IRazorCohostDynamicRegistrationService? _dynamicRegistrationService; + private readonly IClientLanguageServerManager _clientLanguageServerManager; + + public RazorDynamicRegistrationService(IRazorCohostDynamicRegistrationService? dynamicRegistrationService, IClientLanguageServerManager clientLanguageServerManager) + { + _dynamicRegistrationService = dynamicRegistrationService; + _clientLanguageServerManager = clientLanguageServerManager; + } + + public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + if (_dynamicRegistrationService is null) + { + return Task.CompletedTask; + } + + // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL + var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities); + var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager); + + return _dynamicRegistrationService.RegisterAsync(serializedClientCapabilities, razorCohostClientLanguageServerManager, cancellationToken); + } + } +} From 9a6c2eda81d94f9611b17faec72c0dd9553249a2 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 20 Mar 2024 16:22:01 +1100 Subject: [PATCH 0079/1047] Remove obsolete method --- .../Razor/Api/RazorBrokeredServiceImplementation.cs | 4 ---- 1 file changed, 4 deletions(-) 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); } From 34c2a8665e8843e5f535708a58c194fbd5b55b51 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 22 Mar 2024 16:17:46 +1100 Subject: [PATCH 0080/1047] Expose semantic tokens refresh queue so Razor can initialize it --- .../SemanticTokensRefreshQueue.cs | 15 +++++-- .../RazorSemanticTokensRefreshQueueWrapper.cs | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs 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/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs new file mode 100644 index 0000000000000..6510b1934748f --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +[ExportRazorLspServiceFactory(typeof(RazorSemanticTokensRefreshQueueWrapper)), 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) : ILspService, IDisposable + { + public void Initialize(ClientCapabilities clientCapabilities) + => semanticTokensRefreshQueue.Initialize(clientCapabilities); + + public Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken) + => semanticTokensRefreshQueue.TryEnqueueRefreshComputationAsync(project, cancellationToken); + + public void Dispose() + { + semanticTokensRefreshQueue.Dispose(); + } + } +} From 0c6e18da80e887bd85a1858fc2cf29600f072c6f Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 25 Mar 2024 08:55:36 +1100 Subject: [PATCH 0081/1047] Pass the request context through so that Razor can get whatever services it needs --- .../Razor/Cohost/IRazorCohostDynamicRegistrationService.cs | 2 +- .../Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs b/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs index 888b7ce6737f7..94156a79011a4 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/IRazorCohostDynamicRegistrationService.cs @@ -9,5 +9,5 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; internal interface IRazorCohostDynamicRegistrationService { - Task RegisterAsync(string serializedClientCapabilities, IRazorCohostClientLanguageServerManager razorCohostClientLanguageServerManager, CancellationToken cancellationToken); + Task RegisterAsync(string serializedClientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index 4a94540990fd4..f80277464741f 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -48,7 +48,8 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities); var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager); - return _dynamicRegistrationService.RegisterAsync(serializedClientCapabilities, razorCohostClientLanguageServerManager, cancellationToken); + var requestContext = new RazorCohostRequestContext(context); + return _dynamicRegistrationService.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken); } } } From 8ed801832e576f6ddbdf6309fc7eabd3048f2d86 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 25 Mar 2024 17:15:18 +1100 Subject: [PATCH 0082/1047] Fix MEF things --- .../IRazorSemanticTokensRefreshQueue.cs | 16 +++++++++++ ...ohostClientLanguageServerManagerFactory.cs | 3 ++- ...erverClientLanguageServerManagerFactory.cs | 27 +++++++++++++++++++ .../RazorSemanticTokensRefreshQueueWrapper.cs | 12 ++++++--- 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/RazorCohostServerClientLanguageServerManagerFactory.cs diff --git a/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs b/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs new file mode 100644 index 0000000000000..fffbe50e64e6d --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; + +internal interface IRazorSemanticTokensRefreshQueue : ILspService +{ + void Initialize(string clientCapabilitiesString); + + Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs index 7491f391c156d..2a06fa9b68cbd 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorCohostClientLanguageServerManagerFactory.cs @@ -9,7 +9,8 @@ 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 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/RazorSemanticTokensRefreshQueueWrapper.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs index 6510b1934748f..fa227306eb6a9 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs @@ -9,11 +9,12 @@ 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; -[ExportRazorLspServiceFactory(typeof(RazorSemanticTokensRefreshQueueWrapper)), Shared] +[ExportCohostLspServiceFactory(typeof(IRazorSemanticTokensRefreshQueue)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class RazorSemanticTokensRefreshQueueWrapperFactory() : ILspServiceFactory @@ -25,10 +26,13 @@ public ILspService CreateILspService(LspServices lspServices, WellKnownLspServer return new RazorSemanticTokensRefreshQueueWrapper(semanticTokensRefreshQueue); } - internal class RazorSemanticTokensRefreshQueueWrapper(SemanticTokensRefreshQueue semanticTokensRefreshQueue) : ILspService, IDisposable + internal class RazorSemanticTokensRefreshQueueWrapper(SemanticTokensRefreshQueue semanticTokensRefreshQueue) : IRazorSemanticTokensRefreshQueue, IDisposable { - public void Initialize(ClientCapabilities clientCapabilities) - => semanticTokensRefreshQueue.Initialize(clientCapabilities); + public void Initialize(string clientCapabilitiesString) + { + var clientCapabilities = JsonConvert.DeserializeObject(clientCapabilitiesString) ?? new(); + semanticTokensRefreshQueue.Initialize(clientCapabilities); + } public Task TryEnqueueRefreshComputationAsync(Project project, CancellationToken cancellationToken) => semanticTokensRefreshQueue.TryEnqueueRefreshComputationAsync(project, cancellationToken); From f82f5adf7cf732b195a64d010e53e2a64248b82d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 15:39:31 -0700 Subject: [PATCH 0083/1047] Apply suggestions from code review --- .../Remote/ServiceHub/Host/AssetProvider.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 0beae0615e955..976473d3d05f5 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -23,14 +23,6 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { - private const string s_logFile = @"c:\temp\sync\synclog.txt"; - private static readonly SharedStopwatch s_start = SharedStopwatch.StartNew(); - - static AssetProvider() - { - IOUtilities.PerformIO(() => File.Delete(s_logFile)); - } - private const int PooledChecksumArraySize = 256; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); @@ -303,8 +295,6 @@ public async ValueTask SynchronizeAssetsAsync( { var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); - var stopwatch = SharedStopwatch.StartNew(); - await RequestAssetsAsync( assetPath, missingChecksumsMemory, static ( @@ -319,17 +309,6 @@ await RequestAssetsAsync( }, (this, missingChecksums, callback, arg), cancellationToken).ConfigureAwait(false); - - var time = stopwatch.Elapsed; - var totalTime = s_start.Elapsed; - - IOUtilities.PerformIO(() => - { - lock (this) - { - File.AppendAllText(s_logFile, $"{missingChecksumsCount},{checksums.Count},{time},{totalTime},{typeof(T).Name}\r\n"); - } - }); } } finally From 58ae336ea478128eb31516f0cc2f0039a4670251 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 15:42:28 -0700 Subject: [PATCH 0084/1047] revert --- .../Host/RemoteWorkspace.SolutionCreator.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index b8a25fbe80c09..c1c2602dec1c2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -612,27 +612,19 @@ private async Task UpdateDocumentsAsync( } // changed document - using var _ = PooledHashSet.GetInstance(out var checksums); foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) { - if (oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) - checksums.Add(newDocumentChecksums.Info); - } - - await _assetProvider.GetAssetsAsync( - AssetPath.ProjectAndDocuments(project.Id), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - - foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) - { - if (oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) + if (!oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) { - Contract.ThrowIfTrue(oldDocumentChecksums.Checksum == newDocumentChecksums.Checksum); + continue; + } - var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); - Contract.ThrowIfNull(document); + Contract.ThrowIfTrue(oldDocumentChecksums.Checksum == newDocumentChecksums.Checksum); - project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); - } + 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; From c63c074cf20467b5457784311bfd73ac4009b979 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 16:40:52 -0700 Subject: [PATCH 0085/1047] remove log helper --- src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index 0a5b669b9ae69..c394b17331d26 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -98,9 +98,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) From 174b65ebc4d8c89c31cd2e33ba54a6f093eff219 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 10 Apr 2024 17:00:42 -0700 Subject: [PATCH 0086/1047] Remove dictionary of default value --- .../CSharpUnifiedSettingsTests.cs | 52 ++++++++++++++++--- .../UnifiedSettings/UnifiedSettingsTests.vb | 49 +++++++---------- 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 30f603b4a4dc4..855e810cadafd 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -34,18 +34,54 @@ public class CSharpUnifiedSettingsTests : UnifiedSettingsTests CompletionOptionsStorage.ShowNewSnippetExperienceUserOption ); - internal override ImmutableDictionary OptionsToDefaultValue => ImmutableDictionary.Empty. - Add(CompletionOptionsStorage.SnippetsBehavior, SnippetsRule.AlwaysInclude). - Add(CompletionOptionsStorage.EnterKeyBehavior, EnterKeyRule.Never). - Add(CompletionOptionsStorage.TriggerOnDeletion, false). - Add(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, true). - Add(CompletionViewOptionsStorage.EnableArgumentCompletionSnippets, false). - Add(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, false); - internal override ImmutableDictionary> EnumOptionsToValues => ImmutableDictionary>.Empty. Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)). Add(CompletionOptionsStorage.EnterKeyBehavior, ImmutableArray.Create(EnterKeyRule.Never, EnterKeyRule.AfterFullyTypedWord, EnterKeyRule.Always)); + 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() { diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb index d6e393ea34c74..e54e2018bfabf 100644 --- a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -16,18 +16,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Onboarded options in Unified Settings registration file Friend MustOverride ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) - ' The default value of some enum options is overridden at runtime. - ' It could be - ' 1: Option has different default value for C# and VB. - ' 2: Option is in experiment, so it is set to null and override to a value. - ' But in unified settings we always use the correct value for language. - ' Use this dictionary to indicate that value in unit test. - Friend MustOverride ReadOnly Property OptionsToDefaultValue As ImmutableDictionary(Of IOption2, Object) - ' The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. ' Use this dictionary to list all the possible enum values in settings page. Friend MustOverride ReadOnly Property EnumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) + ' 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 + Protected Sub TestUnifiedSettingsCategory(registrationJsonObject As JObject, categoryBasePath As String, languageName As String) Dim actualAllSettings = registrationJsonObject.SelectToken($"$.properties").Children.OfType(Of JProperty). Where(Function(setting) setting.Name.StartsWith(categoryBasePath)). @@ -46,6 +43,11 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings 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) @@ -60,23 +62,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End Sub Private Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) - ' Verify default value - Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") - Dim perLangDefaultValue As Object = Nothing - Assert.Equal(If(OptionsToDefaultValue.TryGetValue([option], perLangDefaultValue), - perLangDefaultValue.ToString().ToCamelCase(), - [option].Definition.DefaultValue?.ToString().ToCamelCase()), actualDefaultValue.ToString().ToCamelCase()) - VerifyMigration(registrationJsonObject, unifiedSettingPath, [option], languageName) End Sub Private Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) - Dim actualDefaultValue = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').default") - Dim perLangDefaultValue As Object = Nothing - Assert.Equal(If(OptionsToDefaultValue.TryGetValue([option], perLangDefaultValue), - perLangDefaultValue.ToString().ToCamelCase(), - [option].Definition.DefaultValue?.ToString()), actualDefaultValue.ToString().ToCamelCase()) - Dim actualEnumValues = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').enum").SelectAsArray(Function(token) token.ToString()) Dim possibleEnumValues As ImmutableArray(Of Object) = ImmutableArray(Of Object).Empty Dim expectedEnumValues = If(EnumOptionsToValues.TryGetValue([option], possibleEnumValues), @@ -193,8 +182,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Dim enumValues = [option].Type.GetEnumValues().Cast(Of Object).ToDictionary( keySelector:=Function(enumValue) enumValue.ToString().ToCamelCase(), elementSelector:=Function(enumValue) - Dim actualDefaultValue As Object = Nothing - If OptionsToDefaultValue.TryGetValue([option], actualDefaultValue) AndAlso actualDefaultValue.Equals(enumValue) Then + 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. @@ -214,14 +203,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' 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 As Object = Nothing - If OptionsToDefaultValue.TryGetValue([option], realDefaultValue) Then - 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 If + 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 From fdacf2931b01c3942d59cb5d8b805579d95b154c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 11 Apr 2024 10:34:16 +1000 Subject: [PATCH 0087/1047] Update RazorMethod to allow specifying language Previously we didn't need this because CLaSP wasn't a source package --- .../ExternalAccess/Razor/Cohost/RazorMethodAttribute.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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) + { + } } From 0b374f05588b3007800af311292d0a5799fa5551 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Wed, 10 Apr 2024 17:37:02 -0700 Subject: [PATCH 0088/1047] Remove the enum default value dictionary --- .../CSharpUnifiedSettingsTests.cs | 23 ++++++++++++++++--- .../UnifiedSettings/UnifiedSettingsTests.vb | 18 +++++++-------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 855e810cadafd..4258c8a03cd5b 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.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.IO; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -34,9 +36,24 @@ public class CSharpUnifiedSettingsTests : UnifiedSettingsTests CompletionOptionsStorage.ShowNewSnippetExperienceUserOption ); - internal override ImmutableDictionary> EnumOptionsToValues => ImmutableDictionary>.Empty. - Add(CompletionOptionsStorage.SnippetsBehavior, ImmutableArray.Create(SnippetsRule.NeverInclude, SnippetsRule.AlwaysInclude, SnippetsRule.IncludeAfterTypingIdentifierQuestionTab)). - Add(CompletionOptionsStorage.EnterKeyBehavior, ImmutableArray.Create(EnterKeyRule.Never, EnterKeyRule.AfterFullyTypedWord, EnterKeyRule.Always)); + 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) { diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb index e54e2018bfabf..784b25db0ed4c 100644 --- a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -16,15 +16,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings ' Onboarded options in Unified Settings registration file Friend MustOverride ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) - ' The default value of some enum options is overridden at runtime. And it's not shown in the unified settings page. - ' Use this dictionary to list all the possible enum values in settings page. - Friend MustOverride ReadOnly Property EnumOptionsToValues As ImmutableDictionary(Of IOption2, ImmutableArray(Of Object)) - ' 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) Dim actualAllSettings = registrationJsonObject.SelectToken($"$.properties").Children.OfType(Of JProperty). Where(Function(setting) setting.Name.StartsWith(categoryBasePath)). @@ -66,11 +69,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings End Sub Private Sub VerifyEnum(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) - Dim actualEnumValues = registrationJsonObject.SelectToken($"$.properties('{unifiedSettingPath}').enum").SelectAsArray(Function(token) token.ToString()) - Dim possibleEnumValues As ImmutableArray(Of Object) = ImmutableArray(Of Object).Empty - Dim expectedEnumValues = If(EnumOptionsToValues.TryGetValue([option], possibleEnumValues), - possibleEnumValues.SelectAsArray(Function(value) value.ToString().ToCamelCase()), - [option].Type.GetEnumValues().Cast(Of String).SelectAsArray(Function(value) value.ToCamelCase())) + 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 From cdc412a7f9aff9914f33bbbce97d59bc368cec13 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 17:46:12 -0700 Subject: [PATCH 0089/1047] rename file --- .../Portable/Workspace/Solution/{AssetHint.cs => AssetPath.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Solution/{AssetHint.cs => AssetPath.cs} (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs From 2439f04260fe860c7bdf5c3035b569b0f17b3de7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 17:54:19 -0700 Subject: [PATCH 0090/1047] Search less --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 12 ++++++++++-- .../Remote/ServiceHub/Host/AssetProvider.cs | 4 ++-- .../Host/RemoteWorkspace.SolutionCreator.cs | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 76f3dd03e497c..c33f0336c071b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -37,8 +37,16 @@ internal readonly struct AssetPath [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; @@ -78,8 +86,8 @@ public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) /// /// /// - public static AssetPath ProjectAndDocuments(ProjectId projectId) - => new(AssetPathKind.Projects | AssetPathKind.Documents, projectId); + public static AssetPath DocumentsInProject(ProjectId projectId) + => new(AssetPathKind.Documents, projectId); [Flags] private enum AssetPathKind diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 2c3f473dd2931..bb59dcfc23c45 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -201,7 +201,7 @@ async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksu // First, fetch all the DocumentStateChecksums for all the documents in the project. using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, + assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), checksums, static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), allDocumentStateChecksums, cancellationToken).ConfigureAwait(false); @@ -215,7 +215,7 @@ await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index c1c2602dec1c2..f8ee4c0fd999c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -544,7 +544,7 @@ private async Task UpdateDocumentsAsync( newChecksumsToSync.AddRange(newDocumentIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.ProjectAndDocuments(project.Id), newChecksumsToSync, + assetPath: AssetPath.DocumentsInProject(project.Id), newChecksumsToSync, static (checksum, documentStateChecksum, newDocumentIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != documentStateChecksum.Checksum); From 939b9c4aea36529162bddf755052665b7b721455 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 18:03:45 -0700 Subject: [PATCH 0091/1047] rename --- .../Portable/Workspace/Solution/AssetPath.cs | 35 +++++++------------ .../Workspace/Solution/StateChecksums.cs | 28 +-------------- .../Remote/ServiceHub/Host/AssetProvider.cs | 6 ++-- .../Host/RemoteWorkspace.SolutionCreator.cs | 6 ++-- 4 files changed, 19 insertions(+), 56 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index c33f0336c071b..dee569c8fb316 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -22,18 +22,21 @@ internal readonly struct AssetPath public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); /// - /// Instance that will only look up solution-level, as well as the top level nodes for projects when searching for - /// checksums. It will not descend into projects. + /// Instance that will only look up solution-level, as well and projects when searching for checksums. It will not + /// descend into documents. /// - public static readonly AssetPath SolutionAndTopLevelProjectsOnly = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects); + public static readonly AssetPath SolutionAndProjects = new(AssetPathKind.Solution | AssetPathKind.Projects); - public static readonly AssetPath ProjectOnly = new(AssetPathKind.Projects); + /// + /// Only search at the project level when searching for checksums. + /// + public static readonly AssetPath ProjectsOnly = new(AssetPathKind.Projects); /// /// 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 = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects | AssetPathKind.Projects | AssetPathKind.Documents | AssetPathKind.Testing); + public static readonly AssetPath FullLookupForTesting = new(AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Documents); [DataMember(Order = 0)] private readonly AssetPathKind _kind; @@ -58,7 +61,6 @@ private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? d } public bool IncludeSolution => (_kind & AssetPathKind.Solution) == AssetPathKind.Solution; - public bool IncludeTopLevelProjects => (_kind & AssetPathKind.TopLevelProjects) == AssetPathKind.TopLevelProjects; public bool IncludeProjects => (_kind & AssetPathKind.Projects) == AssetPathKind.Projects; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) == AssetPathKind.Documents; @@ -78,11 +80,10 @@ private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? d /// /// public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) - => new(AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Testing, projectId); + => new(AssetPathKind.Solution | AssetPathKind.Projects, projectId); /// - /// Searches the requested project, and all documents underneath it. used during normal sync when bulk syncing a - /// project. + /// Searches all documents within the specified project. /// /// /// @@ -97,26 +98,14 @@ private enum AssetPathKind /// Solution = 1 << 0, - /// - /// Search projects, without descending into them. In effect, only finding direct ProjectStateChecksum children - /// of the solution. - /// - TopLevelProjects = 1 << 1, - /// /// Search projects for results. /// - Projects = 1 << 2, + Projects = 1 << 1, /// /// Search documents for results. /// - Documents = 1 << 3, - - /// - /// Indicates that this is a special search performed during testing. These searches are allowed to search - /// everything for expediency purposes. - /// - Testing = 1 << 4, + Documents = 1 << 2, } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2c6d096818bc0..7bc3f20a7e7a5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -257,32 +257,6 @@ public async Task FindAsync( ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); } - if (assetPath.IncludeTopLevelProjects) - { - // Caller is trying to fetch the top level ProjectStateChecksums as well. Look for those without diving deeper. - 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; - - if (projectState.TryGetStateChecksums(out var projectStateChecksums)) - { - await projectStateChecksums.FindAsync( - projectState, - // Only search the project level info of the project we're diving into. Do not go into documents. - AssetPath.ProjectOnly, - searchingChecksumsLeft, - result, - cancellationToken).ConfigureAwait(false); - } - } - } - if (searchingChecksumsLeft.Count == 0) return; @@ -304,7 +278,7 @@ await projectStateChecksums.FindAsync( } else { - // Full search, used for test purposes. + // Check all projects for the remaining checksums. foreach (var (projectId, projectState) in solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index bb59dcfc23c45..b288b237bbc81 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -105,7 +105,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() using var _2 = PooledDictionary.GetInstance(out var checksumToObjects); await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, + assetPath: AssetPath.SolutionAndProjects, checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); @@ -174,7 +174,7 @@ async Task SynchronizeProjectAssetAsync(Func( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.SolutionAndProjects, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } async Task SynchronizeProjectAssetCollectionAsync(Func getChecksums, CancellationToken cancellationToken) @@ -186,7 +186,7 @@ async Task SynchronizeProjectAssetCollectionAsync(Func( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.SolutionAndProjects, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index f8ee4c0fd999c..a99ee4bf1c662 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -220,7 +220,7 @@ private async Task UpdateProjectsAsync( newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, newChecksumsToSync, + assetPath: AssetPath.ProjectsOnly, newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); @@ -253,14 +253,14 @@ private async Task UpdateProjectsAsync( projectItemChecksums.Add(newProjectChecksums.Info); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); projectItemChecksums.Clear(); foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) projectItemChecksums.Add(newProjectChecksums.CompilationOptions); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); From 357bfebfbfbdf77b294009ab5d73d4b5af035473 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 17:46:12 -0700 Subject: [PATCH 0092/1047] rename file --- .../Portable/Workspace/Solution/{AssetHint.cs => AssetPath.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Solution/{AssetHint.cs => AssetPath.cs} (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/AssetHint.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs From c80850992a458ee17b57ceee6aa17c3c34932a1d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 17:54:19 -0700 Subject: [PATCH 0093/1047] Search less --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 12 ++++++++++-- .../Remote/ServiceHub/Host/AssetProvider.cs | 4 ++-- .../Host/RemoteWorkspace.SolutionCreator.cs | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 634f641fa9948..c44943e1ac011 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -35,8 +35,16 @@ internal readonly struct AssetPath [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; @@ -76,8 +84,8 @@ public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) /// /// /// - public static AssetPath ProjectAndDocuments(ProjectId projectId) - => new(AssetPathKind.Projects | AssetPathKind.Documents, projectId); + public static AssetPath DocumentsInProject(ProjectId projectId) + => new(AssetPathKind.Documents, projectId); [Flags] private enum AssetPathKind diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 976473d3d05f5..80eb2347692c8 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -213,7 +213,7 @@ await this.SynchronizeAssetsAsync( // First synchronize all the top-level info about this project. await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, + assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), checksums, static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), allDocumentStateChecksums, cancellationToken).ConfigureAwait(false); @@ -227,7 +227,7 @@ await this.SynchronizeAssetsAsync( - assetPath: AssetPath.ProjectAndDocuments(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index c1c2602dec1c2..f8ee4c0fd999c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -544,7 +544,7 @@ private async Task UpdateDocumentsAsync( newChecksumsToSync.AddRange(newDocumentIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.ProjectAndDocuments(project.Id), newChecksumsToSync, + assetPath: AssetPath.DocumentsInProject(project.Id), newChecksumsToSync, static (checksum, documentStateChecksum, newDocumentIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != documentStateChecksum.Checksum); From 542020ed57afaf1b41b9dd17688a36d8881beeec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 18:03:45 -0700 Subject: [PATCH 0094/1047] rename --- .../Portable/Workspace/Solution/AssetPath.cs | 35 +++++++------------ .../Workspace/Solution/StateChecksums.cs | 32 +---------------- .../Remote/ServiceHub/Host/AssetProvider.cs | 14 ++++---- .../Host/RemoteWorkspace.SolutionCreator.cs | 6 ++-- 4 files changed, 24 insertions(+), 63 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index c44943e1ac011..dee569c8fb316 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -22,16 +22,21 @@ internal readonly struct AssetPath public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); /// - /// Instance that will only look up solution-level, as well as the top level nodes for projects when searching for - /// checksums. It will not descend into projects. + /// Instance that will only look up solution-level, as well and projects when searching for checksums. It will not + /// descend into documents. /// - public static readonly AssetPath SolutionAndTopLevelProjectsOnly = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects); + public static readonly AssetPath SolutionAndProjects = new(AssetPathKind.Solution | AssetPathKind.Projects); + + /// + /// Only search at the project level when searching for checksums. + /// + public static readonly AssetPath ProjectsOnly = new(AssetPathKind.Projects); /// /// 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 = new(AssetPathKind.Solution | AssetPathKind.TopLevelProjects | AssetPathKind.Projects | AssetPathKind.Documents | AssetPathKind.Testing); + public static readonly AssetPath FullLookupForTesting = new(AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Documents); [DataMember(Order = 0)] private readonly AssetPathKind _kind; @@ -56,7 +61,6 @@ private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? d } public bool IncludeSolution => (_kind & AssetPathKind.Solution) == AssetPathKind.Solution; - public bool IncludeTopLevelProjects => (_kind & AssetPathKind.TopLevelProjects) == AssetPathKind.TopLevelProjects; public bool IncludeProjects => (_kind & AssetPathKind.Projects) == AssetPathKind.Projects; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) == AssetPathKind.Documents; @@ -76,11 +80,10 @@ private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? d /// /// public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) - => new(AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Testing, projectId); + => new(AssetPathKind.Solution | AssetPathKind.Projects, projectId); /// - /// Searches the requested project, and all documents underneath it. used during normal sync when bulk syncing a - /// project. + /// Searches all documents within the specified project. /// /// /// @@ -95,26 +98,14 @@ private enum AssetPathKind /// Solution = 1 << 0, - /// - /// Search projects, without descending into them. In effect, only finding direct ProjectStateChecksum children - /// of the solution. - /// - TopLevelProjects = 1 << 1, - /// /// Search projects for results. /// - Projects = 1 << 2, + Projects = 1 << 1, /// /// Search documents for results. /// - Documents = 1 << 3, - - /// - /// Indicates that this is a special search performed during testing. These searches are allowed to search - /// everything for expediency purposes. - /// - Testing = 1 << 4, + Documents = 1 << 2, } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 0252b61ca7315..7bc3f20a7e7a5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -257,36 +257,6 @@ public async Task FindAsync( ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); } - if (assetPath.IncludeTopLevelProjects) - { - // Caller is trying to fetch the top level ProjectStateChecksums as well. Look for those without diving deeper. - 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; - - if (projectState.TryGetStateChecksums(out var projectStateChecksums)) - { - if (searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) - result[projectStateChecksums.Checksum] = projectStateChecksums; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.Info)) - result[projectStateChecksums.Info] = projectState.Attributes; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.CompilationOptions)) - result[projectStateChecksums.CompilationOptions] = projectState.CompilationOptions!; - - if (searchingChecksumsLeft.Remove(projectStateChecksums.ParseOptions)) - result[projectStateChecksums.ParseOptions] = projectState.ParseOptions!; - } - } - } - if (searchingChecksumsLeft.Count == 0) return; @@ -308,7 +278,7 @@ public async Task FindAsync( } else { - // Full search, used for test purposes. + // Check all projects for the remaining checksums. foreach (var (projectId, projectState) in solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 80eb2347692c8..ed212c17085f7 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -105,7 +105,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() using var _2 = PooledDictionary.GetInstance(out var checksumToObjects); await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, + assetPath: AssetPath.SolutionAndProjects, checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); @@ -151,7 +151,7 @@ async ValueTask SynchronizeProjectAssetsWorkerAsync() checksums.Add(projectChecksums.Info); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } { @@ -160,7 +160,7 @@ async ValueTask SynchronizeProjectAssetsWorkerAsync() checksums.Add(projectChecksums.CompilationOptions); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } { @@ -169,7 +169,7 @@ await this.SynchronizeAssetsAsync( checksums.Add(projectChecksums.ParseOptions); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } { @@ -178,7 +178,7 @@ await this.SynchronizeAssetsAsync( AddAll(checksums, projectChecksums.ProjectReferences); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } { @@ -187,7 +187,7 @@ await this.SynchronizeAssetsAsync( AddAll(checksums, projectChecksums.MetadataReferences); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } { @@ -196,7 +196,7 @@ await this.SynchronizeAssetsAsync( AddAll(checksums, projectChecksums.AnalyzerReferences); await this.SynchronizeAssetsAsync( - AssetPath.SolutionAndTopLevelProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + AssetPath.ProjectsOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index f8ee4c0fd999c..a99ee4bf1c662 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -220,7 +220,7 @@ private async Task UpdateProjectsAsync( newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, newChecksumsToSync, + assetPath: AssetPath.ProjectsOnly, newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); @@ -253,14 +253,14 @@ private async Task UpdateProjectsAsync( projectItemChecksums.Add(newProjectChecksums.Info); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); projectItemChecksums.Clear(); foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) projectItemChecksums.Add(newProjectChecksums.CompilationOptions); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionAndTopLevelProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPath.ProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); From 842ac9b2fec1c10cae480030dd41e1020a7d49b2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 20:49:36 -0700 Subject: [PATCH 0095/1047] In progress --- .../IRemoteSourceGenerationService.cs | 4 +- ...lutionCompilationState_SourceGenerators.cs | 7 ++- .../RemoteSourceGenerationService.cs | 60 +++++++++++++++++-- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs index d56c7ff41d3bb..d75ed83198f0b 100644 --- a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs @@ -33,10 +33,10 @@ ValueTask> GetContentsAsync( Checksum solutionChecksum, ProjectId projectId, ImmutableArray documentIds, CancellationToken cancellationToken); /// - /// Whether or not the specified has source generators or not. + /// Whether or not the specified analyzer references have source generators or not. /// ValueTask HasGeneratorsAsync( - Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); + Checksum solutionChecksum, ProjectId projectId, ImmutableArray analyzerReferenceChecksums, CancellationToken cancellationToken); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs index 915b3adc72a60..672875731e42b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs @@ -94,6 +94,8 @@ static SourceGeneratorMap ComputeSourceGenerators(ProjectState projectState) public async Task HasSourceGeneratorsAsync(ProjectId projectId, CancellationToken cancellationToken) { var projectState = this.SolutionState.GetRequiredProjectState(projectId); + if (projectState.AnalyzerReferences.Count == 0) + return false; if (!s_hasSourceGeneratorsMap.TryGetValue(projectState, out var lazy)) { @@ -120,10 +122,13 @@ static async Task ComputeHasSourceGeneratorsAsync( // 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, cancellationToken), + (service, solution, cancellationToken) => service.HasGeneratorsAsync(solution, projectId, analyzerReferences, cancellationToken), cancellationToken).ConfigureAwait(false); return result.HasValue && result.Value; } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 6cee072584d86..bd1a9e94f0683 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -3,9 +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.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SourceGeneration; @@ -13,6 +17,8 @@ namespace Microsoft.CodeAnalysis.Remote; +using AnalyzerReferenceMap = ConditionalWeakTable>; + internal sealed partial class RemoteSourceGenerationService(in BrokeredServiceBase.ServiceConstructionArguments arguments) : BrokeredServiceBase(arguments), IRemoteSourceGenerationService { @@ -64,11 +70,57 @@ public ValueTask> GetContentsAsync( }, cancellationToken); } - public ValueTask HasGeneratorsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken) + private static readonly ImmutableArray<(string language, AnalyzerReferenceMap analyzerReferenceMap, AnalyzerReferenceMap.CreateValueCallback callback)> s_languageToAnalyzerReferenceMap = + [ + (LanguageNames.CSharp, new(), static analyzerReference => AsyncLazy.Create(cancellationToken => HasSourceGeneratorsAsync(analyzerReference, LanguageNames.CSharp, cancellationToken))), + (LanguageNames.VisualBasic, new(), static analyzerReference => AsyncLazy.Create(cancellationToken => HasSourceGeneratorsAsync(analyzerReference, LanguageNames.VisualBasic, cancellationToken))) + ]; + + private static async Task HasSourceGeneratorsAsync( + AnalyzerReference analyzerReference, string language, CancellationToken cancellationToken) { - return RunServiceAsync(solutionChecksum, async solution => + var generators = analyzerReference.GetGenerators(langauge); + return generators.Any(); + } + + public async ValueTask HasGeneratorsAsync( + Checksum solutionChecksum, + ProjectId projectId, + ImmutableArray analyzerReferenceChecksums, + string language, + CancellationToken cancellationToken) + { + if (analyzerReferenceChecksums.Length == 0) + return false; + + 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. + using var _2 = ArrayBuilder.GetInstance(checksums.Count, out var analyzerReferences); + await assetProvider.GetAssetsAsync>( + projectId, + checksums, + static (_, analyzerReference, analyzerReferences) => analyzerReferences.Add(analyzerReference), + analyzerReferences, + cancellationToken).ConfigureAwait(false); + + var tuple = s_languageToAnalyzerReferenceMap.Single(static (val, language) => val.language == language, language); + var analyzerReferenceMap = tuple.analyzerReferenceMap; + var callback = tuple.callback; + + foreach (var analyzerReference in analyzerReferences) { - return await solution.CompilationState.HasSourceGeneratorsAsync(projectId, cancellationToken).ConfigureAwait(false); - }, cancellationToken); + var hasGeneratorsLazy = analyzerReferenceMap.GetValue(analyzerReference, callback); + if (await hasGeneratorsLazy.GetValueAsync(cancellationToken).ConfigureAwait(false)) + return true; + } + + return false; } } From e8e9c4993a7a576b378237b9691f859aa7534077 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 20:58:51 -0700 Subject: [PATCH 0096/1047] docs --- .../IRemoteSourceGenerationService.cs | 2 +- .../RemoteSourceGenerationService.cs | 30 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs index d75ed83198f0b..659de283e8849 100644 --- a/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs +++ b/src/Workspaces/Core/Portable/SourceGeneration/IRemoteSourceGenerationService.cs @@ -36,7 +36,7 @@ ValueTask> GetContentsAsync( /// Whether or not the specified analyzer references have source generators or not. /// ValueTask HasGeneratorsAsync( - Checksum solutionChecksum, ProjectId projectId, ImmutableArray analyzerReferenceChecksums, CancellationToken cancellationToken); + Checksum solutionChecksum, ProjectId projectId, ImmutableArray analyzerReferenceChecksums, string language, CancellationToken cancellationToken); } /// diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index bd1a9e94f0683..23245daae557b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -17,7 +17,8 @@ namespace Microsoft.CodeAnalysis.Remote; -using AnalyzerReferenceMap = ConditionalWeakTable>; +// 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 @@ -72,15 +73,15 @@ public ValueTask> GetContentsAsync( private static readonly ImmutableArray<(string language, AnalyzerReferenceMap analyzerReferenceMap, AnalyzerReferenceMap.CreateValueCallback callback)> s_languageToAnalyzerReferenceMap = [ - (LanguageNames.CSharp, new(), static analyzerReference => AsyncLazy.Create(cancellationToken => HasSourceGeneratorsAsync(analyzerReference, LanguageNames.CSharp, cancellationToken))), - (LanguageNames.VisualBasic, new(), static analyzerReference => AsyncLazy.Create(cancellationToken => HasSourceGeneratorsAsync(analyzerReference, LanguageNames.VisualBasic, cancellationToken))) + (LanguageNames.CSharp, new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.CSharp)), + (LanguageNames.VisualBasic, new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.VisualBasic)) ]; - private static async Task HasSourceGeneratorsAsync( - AnalyzerReference analyzerReference, string language, CancellationToken cancellationToken) + private static StrongBox HasSourceGenerators( + AnalyzerReference analyzerReference, string language) { - var generators = analyzerReference.GetGenerators(langauge); - return generators.Any(); + var generators = analyzerReference.GetGenerators(language); + return new(generators.Any()); } public async ValueTask HasGeneratorsAsync( @@ -93,6 +94,11 @@ public async ValueTask HasGeneratorsAsync( 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); @@ -102,6 +108,12 @@ public async ValueTask HasGeneratorsAsync( // 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.GetAssetsAsync>( projectId, @@ -116,8 +128,8 @@ await assetProvider.GetAssetsAsync Date: Wed, 10 Apr 2024 21:12:04 -0700 Subject: [PATCH 0097/1047] Cleanup --- ...lutionCompilationState_SourceGenerators.cs | 33 +++++++++++++------ .../RemoteSourceGenerationService.cs | 15 ++++----- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs index 672875731e42b..ab0b6e27b3d8d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs @@ -3,6 +3,7 @@ // 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; @@ -17,6 +18,11 @@ 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( @@ -38,7 +44,11 @@ private sealed record SourceGeneratorMap( /// process (if present) and having it make the determination, without the host necessarily loading generators /// itself. /// - private static readonly ConditionalWeakTable> s_hasSourceGeneratorsMap = new(); + 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. @@ -97,20 +107,23 @@ public async Task HasSourceGeneratorsAsync(ProjectId projectId, Cancellati if (projectState.AnalyzerReferences.Count == 0) return false; - if (!s_hasSourceGeneratorsMap.TryGetValue(projectState, out var lazy)) + 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(projectState); + lazy = GetLazy(analyzerReferenceMap, projectState); } return await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false); - AsyncLazy GetLazy(ProjectState projectState) - => s_hasSourceGeneratorsMap.GetValue( - projectState, - projectState => AsyncLazy.Create( - static (tuple, cancellationToken) => ComputeHasSourceGeneratorsAsync(tuple.@this, tuple.projectState, cancellationToken), - (@this: this, projectState))); + 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) @@ -128,7 +141,7 @@ static async Task ComputeHasSourceGeneratorsAsync( var result = await client.TryInvokeAsync( solution, projectId, - (service, solution, cancellationToken) => service.HasGeneratorsAsync(solution, projectId, analyzerReferences, cancellationToken), + (service, solution, cancellationToken) => service.HasGeneratorsAsync(solution, projectId, analyzerReferences, projectState.Language, cancellationToken), cancellationToken).ConfigureAwait(false); return result.HasValue && result.Value; } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 23245daae557b..5a5a17d0021a4 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -71,11 +71,11 @@ public ValueTask> GetContentsAsync( }, cancellationToken); } - private static readonly ImmutableArray<(string language, AnalyzerReferenceMap analyzerReferenceMap, AnalyzerReferenceMap.CreateValueCallback callback)> s_languageToAnalyzerReferenceMap = - [ - (LanguageNames.CSharp, new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.CSharp)), - (LanguageNames.VisualBasic, new(), static analyzerReference => HasSourceGenerators(analyzerReference, LanguageNames.VisualBasic)) - ]; + 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) @@ -122,10 +122,7 @@ await assetProvider.GetAssetsAsync val.language == language, language); - var analyzerReferenceMap = tuple.analyzerReferenceMap; - var callback = tuple.callback; - + var (analyzerReferenceMap, callback) = s_languageToAnalyzerReferenceMap[language]; foreach (var analyzerReference in analyzerReferences) { var hasGenerators = analyzerReferenceMap.GetValue(analyzerReference, callback); From df6429d26a3d2997385a960c578c08cccb3f1830 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 21:22:11 -0700 Subject: [PATCH 0098/1047] formatting --- .../Solution/SolutionCompilationState_SourceGenerators.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs index ab0b6e27b3d8d..4a94680e0bd91 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_SourceGenerators.cs @@ -47,7 +47,7 @@ private sealed record SourceGeneratorMap( private static readonly Dictionary s_languageToAnalyzerReferenceMap = new() { { LanguageNames.CSharp, new() }, - {LanguageNames.VisualBasic, new() }, + { LanguageNames.VisualBasic, new() }, }; /// From dc54240c282e1a215794694fc449f59bbb198382 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 22:33:36 -0700 Subject: [PATCH 0099/1047] Simplify ForAttributeWithMetadataName --- ...lueProvider.ImmutableArrayValueComparer.cs | 35 ------------------- ...alueProvider_ForAttributeWithSimpleName.cs | 1 - 2 files changed, 36 deletions(-) delete mode 100644 src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider.ImmutableArrayValueComparer.cs 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..68faac2336808 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs @@ -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 From d584ef96bbb1d4071b6835aa8261f1ec571fa439 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 10 Apr 2024 22:47:04 -0700 Subject: [PATCH 0100/1047] Make static --- .../SyntaxValueProvider_ForAttributeWithSimpleName.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs b/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs index 68faac2336808..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"); @@ -94,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( From b724b71716b028480dd194c6948fa48705c2261b Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 11 Apr 2024 12:29:57 +0000 Subject: [PATCH 0101/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240409.3 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520903 From ef41bae85e51830537bfdc277ef870d9b61a1d96 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 10:19:38 -0700 Subject: [PATCH 0102/1047] Simplify a small piece of project system batching code --- .../ProjectSystem/ProjectSystemProject.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index fd6599b2c1565..6249e92df4226 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -533,35 +533,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 From bd5507128a5553a85736a0560b5833ca8ef31097 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 10:45:34 -0700 Subject: [PATCH 0103/1047] Hybrid mode --- .../Remote/ServiceHub/Host/AssetProvider.cs | 97 ++++++++++++++----- 1 file changed, 74 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index b288b237bbc81..b665134c85f3a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -137,26 +137,73 @@ public async ValueTask SynchronizeProjectAssetsAsync( // consume the data. using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, message: null, cancellationToken)) { - await SynchronizeProjectAssetsWorkerAsync().ConfigureAwait(false); + // 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 parallel, 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) + { + using var _ = ArrayBuilder.GetInstance(allProjectChecksums.Count, out var tasks); + foreach (var singleProjectChecksums in allProjectChecksums) + { + // We want to synchronize the assets just for this project. So we can pass the project down as a + // hint to limit the search on the host side. + AssetPath assetPath = singleProjectChecksums.ProjectId; + + // 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); + + tasks.Add(SynchronizeProjectAssetsWorkerAsync(tempBuffer, assetPath, freeArrayBuilder: true)); + } + + 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. + var assetPath = AssetPath.SolutionAndProjects; + await SynchronizeProjectAssetsWorkerAsync(allProjectChecksums, assetPath, freeArrayBuilder: false).ConfigureAwait(false); + } } - async ValueTask SynchronizeProjectAssetsWorkerAsync() + async Task SynchronizeProjectAssetsWorkerAsync( + ArrayBuilder allProjectChecksums, AssetPath assetPath, bool freeArrayBuilder) { - using var _ = ArrayBuilder.GetInstance(out var tasks); + try + { + await Task.Yield(); - // Make parallel requests for all the project data across all projects at once. - tasks.Add(SynchronizeProjectAssetAsync(static p => p.Info, cancellationToken)); - tasks.Add(SynchronizeProjectAssetAsync(static p => p.CompilationOptions, cancellationToken)); - tasks.Add(SynchronizeProjectAssetAsync(static p => p.ParseOptions, cancellationToken)); - tasks.Add(SynchronizeProjectAssetCollectionAsync(static p => p.ProjectReferences, cancellationToken)); - tasks.Add(SynchronizeProjectAssetCollectionAsync(static p => p.MetadataReferences, cancellationToken)); - tasks.Add(SynchronizeProjectAssetCollectionAsync(static p => p.AnalyzerReferences, cancellationToken)); + using var _ = ArrayBuilder.GetInstance(out var tasks); - // Then sync each project's documents in parallel with each other. - foreach (var projectChecksums in allProjectChecksums) - tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken)); + // Make parallel requests for all the project data across all projects at once. + tasks.Add(SynchronizeProjectAssetAsync(assetPath, static p => p.Info)); + tasks.Add(SynchronizeProjectAssetAsync(assetPath, static p => p.CompilationOptions)); + tasks.Add(SynchronizeProjectAssetAsync(assetPath, static p => p.ParseOptions)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(assetPath, static p => p.ProjectReferences)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(assetPath, static p => p.MetadataReferences)); + tasks.Add(SynchronizeProjectAssetCollectionAsync(assetPath, static p => p.AnalyzerReferences)); - await Task.WhenAll(tasks).ConfigureAwait(false); + // Then sync each project's documents in parallel with each other. + foreach (var projectChecksums in allProjectChecksums) + tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums)); + + await Task.WhenAll(tasks).ConfigureAwait(false); + } + finally + { + if (freeArrayBuilder) + allProjectChecksums.Free(); + } } static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) @@ -165,7 +212,7 @@ static void AddAll(HashSet checksums, ChecksumCollection checksumColle checksums.Add(checksum); } - async Task SynchronizeProjectAssetAsync(Func getChecksum, CancellationToken cancellationToken) + async Task SynchronizeProjectAssetAsync(AssetPath assetPath, Func getChecksum) { await Task.Yield(); using var _ = PooledHashSet.GetInstance(out var checksums); @@ -173,11 +220,10 @@ async Task SynchronizeProjectAssetAsync(Func( - AssetPath.SolutionAndProjects, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await SynchronizeAssetsAsync(assetPath, checksums).ConfigureAwait(false); } - async Task SynchronizeProjectAssetCollectionAsync(Func getChecksums, CancellationToken cancellationToken) + async Task SynchronizeProjectAssetCollectionAsync(AssetPath assetPath, Func getChecksums) { await Task.Yield(); using var _ = PooledHashSet.GetInstance(out var checksums); @@ -185,11 +231,10 @@ async Task SynchronizeProjectAssetCollectionAsync(Func( - AssetPath.SolutionAndProjects, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await SynchronizeAssetsAsync(assetPath, checksums).ConfigureAwait(false); } - async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) + async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums) { await Task.Yield(); using var _1 = PooledHashSet.GetInstance(out var checksums); @@ -214,9 +259,15 @@ await this.SynchronizeAssetsAsync( - assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + // We know we only need to search the documents in this particular project for those info/text values. So + // pass in the right path hint to limit the search on the host side to just the document in this project. + await SynchronizeAssetsAsync( + assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), + checksums).ConfigureAwait(false); } + + ValueTask SynchronizeAssetsAsync(AssetPath assetPath, HashSet checksums) + => this.SynchronizeAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken); } public async ValueTask SynchronizeAssetsAsync( From bc626e2a4441e85e86890a55d740c76cd8c6a83b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 10:54:13 -0700 Subject: [PATCH 0104/1047] Docs --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index b665134c85f3a..e73d135160d32 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -146,10 +146,11 @@ public async ValueTask SynchronizeProjectAssetsAsync( // // 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 parallel, knowing that as we're searching for a ton of + // 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) { From 6cbd1b5cc202f2b5fa6dd796168c497c4c69b698 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:00:02 -0700 Subject: [PATCH 0105/1047] Share code --- .../Remote/ServiceHub/Host/AssetProvider.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index e73d135160d32..79db93e532938 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -213,24 +213,26 @@ static void AddAll(HashSet checksums, ChecksumCollection checksumColle checksums.Add(checksum); } - async Task SynchronizeProjectAssetAsync(AssetPath assetPath, Func getChecksum) + 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) => AddAll(checksums, getChecksums(projectStateChecksums)), + getChecksums); + + async Task SynchronizeProjectAssetOrCollectionAsync( + AssetPath assetPath, Action, TArg> addAllChecksums, TArg arg) { await Task.Yield(); using var _ = PooledHashSet.GetInstance(out var checksums); foreach (var projectChecksums in allProjectChecksums) - checksums.Add(getChecksum(projectChecksums)); - - await SynchronizeAssetsAsync(assetPath, checksums).ConfigureAwait(false); - } - - async Task SynchronizeProjectAssetCollectionAsync(AssetPath assetPath, Func getChecksums) - { - await Task.Yield(); - using var _ = PooledHashSet.GetInstance(out var checksums); - - foreach (var projectChecksums in allProjectChecksums) - AddAll(checksums, getChecksums(projectChecksums)); + addAllChecksums(projectChecksums, checksums, arg); await SynchronizeAssetsAsync(assetPath, checksums).ConfigureAwait(false); } From ad3ba1b87c3d663c59bc304b32ef70b67c4b5861 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 11 Apr 2024 11:10:46 -0700 Subject: [PATCH 0106/1047] Fix analyzer warning --- .../TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb index 784b25db0ed4c..48abfad77a558 100644 --- a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -64,7 +64,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Next End Sub - Private Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) + Private Shared Sub VerifySettings(registrationJsonObject As JObject, unifiedSettingPath As String, [option] As IOption2, languageName As String) VerifyMigration(registrationJsonObject, unifiedSettingPath, [option], languageName) End Sub From 078c2ed5f1a61e763293b750f546521863788fa2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:14:17 -0700 Subject: [PATCH 0107/1047] Add docs --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index ad5f1ca91a42e..37c6881906c63 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -57,6 +57,10 @@ public override async ValueTask GetAssetsAsync( 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 = SharedStopwatch.StartNew(); From e15a73c134c302f198747b00ef4638e3f0e985e5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:17:46 -0700 Subject: [PATCH 0108/1047] optimize --- .../Workspace/Solution/StateChecksums.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 7bc3f20a7e7a5..ea5ffa5db9ba6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -279,6 +279,27 @@ public async Task FindAsync( else { // Check all projects for the remaining checksums. + + // Note: optimize the case where the caller is asking for all the project-state-checksums, but not + // diving any deeper into them. + if (assetPath.IncludeProjects) + { + foreach (var (projectId, projectState) in solution.ProjectStates) + { + if (searchingChecksumsLeft.Count == 0) + break; + + if (projectCone != null && !projectCone.Contains(projectId)) + continue; + + if (projectState.TryGetStateChecksums(out var projectStateChecksums) && + searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) + { + result[projectStateChecksums.Checksum] = projectStateChecksums; + } + } + } + foreach (var (projectId, projectState) in solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) From 69528e5225e2daae03b0fa16164c6ae2b436b4f7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:14:17 -0700 Subject: [PATCH 0109/1047] Add docs --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 79db93e532938..18339d844b2cd 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -58,6 +58,10 @@ public override async ValueTask GetAssetsAsync( 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 = SharedStopwatch.StartNew(); From 21ae618aff04ab3155ea260f52f380a7e28b28b9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:21:06 -0700 Subject: [PATCH 0110/1047] Docs --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 18339d844b2cd..815f56e99b6ca 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -108,6 +108,9 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() using var _2 = PooledDictionary.GetInstance(out var checksumToObjects); + // Note: this search will be optimized on the host side. It will search through the solution level values, + // and then the top level project-state-checksum values first (without diving into the projects). This will + // then find all the checksums, avoiding any need to dive into the projects for a more costly search. await this.SynchronizeAssetsAsync>( assetPath: AssetPath.SolutionAndProjects, checksums, From 79c3cdf571f31f962614921ed78e7baaa67b8b27 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Thu, 11 Apr 2024 11:24:01 -0700 Subject: [PATCH 0111/1047] Remove the accidental change in proj file --- .../Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 99ca1244cb062..7e8b130d74ac7 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -7,11 +7,6 @@ true - - - - - From 510331c01226674068529f9f9f86b47b990ba328 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:28:09 -0700 Subject: [PATCH 0112/1047] Share code --- .../Workspace/Solution/StateChecksums.cs | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index ea5ffa5db9ba6..a673b27c03488 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -284,37 +284,47 @@ public async Task FindAsync( // diving any deeper into them. if (assetPath.IncludeProjects) { - foreach (var (projectId, projectState) in solution.ProjectStates) - { - if (searchingChecksumsLeft.Count == 0) - break; - - if (projectCone != null && !projectCone.Contains(projectId)) - continue; - - if (projectState.TryGetStateChecksums(out var projectStateChecksums) && - searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) + await FindInProjectAsync( + static (_, projectStateChecksums, tuple, _) => { - result[projectStateChecksums.Checksum] = projectStateChecksums; - } - } - } + if (tuple.searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) + tuple.result[projectStateChecksums.Checksum] = projectStateChecksums; - 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, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + return Task.CompletedTask; + }, + (searchingChecksumsLeft, result), + cancellationToken).ConfigureAwait(false); } + + await FindInProjectAsync( + static (projectState, projectStateChecksums, tuple, cancellationToken) => + projectStateChecksums.FindAsync(projectState, tuple.assetPath, tuple.searchingChecksumsLeft, tuple.result, cancellationToken), + (assetPath, searchingChecksumsLeft, result), + cancellationToken).ConfigureAwait(false); + } + } + + return; + + async Task FindInProjectAsync( + Func findInProjectAsync, TArg arg, CancellationToken cancellationToken) + { + foreach (var (projectId, projectState) in solution.ProjectStates) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (searchingChecksumsLeft.Count == 0) + break; + + 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 findInProjectAsync(projectState, projectStateChecksums, arg, cancellationToken).ConfigureAwait(false); } } } From d8eb43a3d8c8284d8dc93ea08aebbd2e28b7bebb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:29:11 -0700 Subject: [PATCH 0113/1047] Docs --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index a673b27c03488..dc7fd197d1a98 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -281,7 +281,9 @@ public async Task FindAsync( // Check all projects for the remaining checksums. // Note: optimize the case where the caller is asking for all the project-state-checksums, but not - // diving any deeper into them. + // diving any deeper into them. Do a first pass just checking the top level project checksum itself. If + // that finds all the remaining items, we'll bail out of the second pass below which dives into the + // projects. if (assetPath.IncludeProjects) { await FindInProjectAsync( @@ -313,6 +315,7 @@ async Task FindInProjectAsync( { cancellationToken.ThrowIfCancellationRequested(); + // If we have no more checksums, can immediately bail out. if (searchingChecksumsLeft.Count == 0) break; From 92654f43dfc523de72ea34f295e57d054d1d0660 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 11:30:35 -0700 Subject: [PATCH 0114/1047] Docs --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index a99ee4bf1c662..2dfe744c68a2e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -273,8 +273,8 @@ await _assetProvider.GetAssetsAsync( projectStateChecksumsToAdd.Add(newProjectChecksums); } - // bulk sync added project assets fully since we'll definitely need that data, and we won't want - + // bulk sync added project assets fully since we'll definitely need that data, and we can fetch more + // efficiently in bulkd and in parallel. await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) From afb12f6188b0d3f5abf818201b10a11f76929213 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:03:54 -0700 Subject: [PATCH 0115/1047] fine grained kinds --- .../Portable/Workspace/Solution/AssetPath.cs | 25 +++++-- .../Workspace/Solution/StateChecksums.cs | 73 ++++++------------- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index dee569c8fb316..7c8c271de923c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -60,9 +60,15 @@ private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? d DocumentId = documentId; } - public bool IncludeSolution => (_kind & AssetPathKind.Solution) == AssetPathKind.Solution; - public bool IncludeProjects => (_kind & AssetPathKind.Projects) == AssetPathKind.Projects; - public bool IncludeDocuments => (_kind & AssetPathKind.Documents) == AssetPathKind.Documents; + public bool IncludeSolution => (_kind & AssetPathKind.Solution) != 0; + public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; + public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 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; /// /// Searches only for information about this project. @@ -98,14 +104,21 @@ private enum AssetPathKind /// Solution = 1 << 0, + ProjectAttributes = 1 << 1, + ProjectCompilationOptions = 1 << 2, + ProjectParseOptions = 1 << 3, + ProjectProjectReferences = 1 << 4, + ProjectMetadataReferences = 1 << 5, + ProjectAnalyzerReferences = 1 << 6, + /// - /// Search projects for results. + /// Search projects for results. All project-level information will be searched. /// - Projects = 1 << 1, + Projects = ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, /// /// Search documents for results. /// - Documents = 1 << 2, + Documents = 1 << 7, } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index dc7fd197d1a98..46ffd86008e08 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -280,54 +280,24 @@ public async Task FindAsync( { // Check all projects for the remaining checksums. - // Note: optimize the case where the caller is asking for all the project-state-checksums, but not - // diving any deeper into them. Do a first pass just checking the top level project checksum itself. If - // that finds all the remaining items, we'll bail out of the second pass below which dives into the - // projects. - if (assetPath.IncludeProjects) + foreach (var (projectId, projectState) in solution.ProjectStates) { - await FindInProjectAsync( - static (_, projectStateChecksums, tuple, _) => - { - if (tuple.searchingChecksumsLeft.Remove(projectStateChecksums.Checksum)) - tuple.result[projectStateChecksums.Checksum] = projectStateChecksums; - - return Task.CompletedTask; - }, - (searchingChecksumsLeft, result), - cancellationToken).ConfigureAwait(false); - } - - await FindInProjectAsync( - static (projectState, projectStateChecksums, tuple, cancellationToken) => - projectStateChecksums.FindAsync(projectState, tuple.assetPath, tuple.searchingChecksumsLeft, tuple.result, cancellationToken), - (assetPath, searchingChecksumsLeft, result), - cancellationToken).ConfigureAwait(false); - } - } - - return; - - async Task FindInProjectAsync( - Func findInProjectAsync, TArg arg, CancellationToken cancellationToken) - { - foreach (var (projectId, projectState) in solution.ProjectStates) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - // If we have no more checksums, can immediately bail out. - if (searchingChecksumsLeft.Count == 0) - break; + // If we have no more checksums, can immediately bail out. + if (searchingChecksumsLeft.Count == 0) + break; - if (projectCone != null && !projectCone.Contains(projectId)) - continue; + 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; + // 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 findInProjectAsync(projectState, projectStateChecksums, arg, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + } } } } @@ -455,24 +425,29 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (searchingChecksumsLeft.Remove(Info)) + if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) result[Info] = state.ProjectInfo.Attributes; - if (searchingChecksumsLeft.Remove(CompilationOptions)) + if (assetPath.IncludeProjectCompilationOptions && searchingChecksumsLeft.Remove(CompilationOptions)) { Contract.ThrowIfNull(state.CompilationOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); result[CompilationOptions] = state.CompilationOptions; } - if (searchingChecksumsLeft.Remove(ParseOptions)) + if (assetPath.IncludeProjectParseOptions && searchingChecksumsLeft.Remove(ParseOptions)) { Contract.ThrowIfNull(state.ParseOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); result[ParseOptions] = state.ParseOptions; } - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + if (assetPath.IncludeProjectProjectReferences) + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); + + if (assetPath.IncludeProjectMetadataReferences) + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); + + if (assetPath.IncludeProjectAnalyzerReferences) + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); } if (assetPath.IncludeDocuments) From d49c48401a9fa0a4a1c094e29ea41c87bfcff233 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:06:55 -0700 Subject: [PATCH 0116/1047] Simplify pattern --- .../Portable/Workspace/Solution/AssetPath.cs | 18 ++++++++++-------- .../Workspace/Solution/StateChecksums.cs | 17 +++-------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 7c8c271de923c..066e72e4ea725 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -63,6 +63,7 @@ private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? d public bool IncludeSolution => (_kind & AssetPathKind.Solution) != 0; public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; + public bool IncludeProjectChecksums => (_kind & AssetPathKind.ProjectChecksums) != 0; public bool IncludeProjectAttributes => (_kind & AssetPathKind.ProjectAttributes) != 0; public bool IncludeProjectCompilationOptions => (_kind & AssetPathKind.ProjectCompilationOptions) != 0; public bool IncludeProjectParseOptions => (_kind & AssetPathKind.ProjectParseOptions) != 0; @@ -104,21 +105,22 @@ private enum AssetPathKind /// Solution = 1 << 0, - ProjectAttributes = 1 << 1, - ProjectCompilationOptions = 1 << 2, - ProjectParseOptions = 1 << 3, - ProjectProjectReferences = 1 << 4, - ProjectMetadataReferences = 1 << 5, - ProjectAnalyzerReferences = 1 << 6, + ProjectChecksums = 1 << 1, + ProjectAttributes = 1 << 2, + ProjectCompilationOptions = 1 << 3, + ProjectParseOptions = 1 << 4, + ProjectProjectReferences = 1 << 5, + ProjectMetadataReferences = 1 << 6, + ProjectAnalyzerReferences = 1 << 7, /// /// Search projects for results. All project-level information will be searched. /// - Projects = ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, + Projects = ProjectChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, /// /// Search documents for results. /// - Documents = 1 << 7, + Documents = 1 << 8, } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 46ffd86008e08..2819a8d0e29c8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -417,28 +417,17 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { - if (searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeProjectChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; - // 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)) result[Info] = state.ProjectInfo.Attributes; if (assetPath.IncludeProjectCompilationOptions && 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; - } + result[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."); if (assetPath.IncludeProjectParseOptions && searchingChecksumsLeft.Remove(ParseOptions)) - { - Contract.ThrowIfNull(state.ParseOptions, "We should not be trying to serialize a project with no compilation options; RemoteSupportedLanguages.IsSupported should have filtered it out."); - result[ParseOptions] = state.ParseOptions; - } + result[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."); if (assetPath.IncludeProjectProjectReferences) ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); From dc8ed7b2baa31ca5764f35ca215979c34b07a2ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:08:33 -0700 Subject: [PATCH 0117/1047] Only search solutions and project checksums --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 6 +++--- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 066e72e4ea725..eb66c72c50268 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -22,10 +22,10 @@ internal readonly struct AssetPath public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); /// - /// Instance that will only look up solution-level, as well and projects when searching for checksums. It will not - /// descend into documents. + /// Instance that will only look up solution-level, as well ProjectStateChecksums when searching for checksums. It + /// will not descend into any other project data, including not descending into documents. /// - public static readonly AssetPath SolutionAndProjects = new(AssetPathKind.Solution | AssetPathKind.Projects); + public static readonly AssetPath SolutionAndProjectChecksums = new(AssetPathKind.Solution | AssetPathKind.ProjectChecksums); /// /// Only search at the project level when searching for checksums. diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 815f56e99b6ca..6b9cac223479d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -109,10 +109,10 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() using var _2 = PooledDictionary.GetInstance(out var checksumToObjects); // Note: this search will be optimized on the host side. It will search through the solution level values, - // and then the top level project-state-checksum values first (without diving into the projects). This will - // then find all the checksums, avoiding any need to dive into the projects for a more costly search. + // and then the top level project-state-checksum values only. No other project data or document data will be + // looked at. await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.SolutionAndProjects, + AssetPath.SolutionAndProjectChecksums, checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); From 5a4904557caa9d58716ad8206b58e50960bdfaf4 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 11 Apr 2024 12:12:48 -0700 Subject: [PATCH 0118/1047] Restore `dynamic` as result type of some operations involving `dynamic` arguments (#72964) Fixes #72750. For C# 12 language version: behavior of the compiler in regards to deciding between whether binding should be static or dynamic is the same as behavior of C# 12 compiler. As a result, for affected scenarios, what used to have `dynamic` type in C# 12 compiler will have `dynamic` type when C# 12 language version is targeted. For newer language versions: invocations statically bound in presence of dynamic arguments should have dynamic result if their dynamic binding succeeded in C# 12. Corresponding spec update - dotnet/csharplang#8027 at commit 8. Related to #33011, #72906, #72912, #72913, #72914, #72916, #72931. --- .../UseCollectionExpressionForArrayTests.cs | 56 +- .../Portable/Binder/Binder.ValueChecks.cs | 30 +- .../Portable/Binder/Binder_Deconstruct.cs | 63 +- .../Portable/Binder/Binder_Expressions.cs | 56 +- .../Portable/Binder/Binder_Invocation.cs | 86 +- .../Portable/Binder/Binder_Operators.cs | 78 +- .../Portable/Binder/Binder_Statements.cs | 9 +- .../Compilation/CSharpSemanticModel.cs | 5 +- .../Portable/FlowAnalysis/NullableWalker.cs | 155 +- .../LocalRewriter_AssignmentOperator.cs | 61 +- .../LocalRewriter/LocalRewriter_Call.cs | 26 +- ...ocalRewriter_CompoundAssignmentOperator.cs | 7 +- .../LocalRewriter/LocalRewriter_Conversion.cs | 6 +- ...writer_DeconstructionAssignmentOperator.cs | 42 +- .../LocalRewriter/LocalRewriter_Event.cs | 9 +- .../LocalRewriter_IndexerAccess.cs | 26 +- .../LocalRewriter_LocalDeclaration.cs | 1 - ...writer_NullCoalescingAssignmentOperator.cs | 12 +- ...ObjectOrCollectionInitializerExpression.cs | 17 +- .../LocalRewriter_UnaryOperator.cs | 204 +- .../LocalRewriter_UsingStatement.cs | 2 +- .../Lowering/SyntheticBoundNodeFactory.cs | 16 +- ...perationTests_IObjectCreationExpression.cs | 36 + .../Test/Semantic/Semantics/DynamicTests.cs | 7007 ++++++++++++++++- .../Test/Semantic/Semantics/OperatorTests.cs | 10 +- ...nticModelGetSemanticInfoTests_LateBound.cs | 6 +- 26 files changed, 7751 insertions(+), 275 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs index e52dfc7a0fd93..fec3c88739190 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs @@ -5318,7 +5318,7 @@ private void Test1(dynamic obj, params int?[][] args) } } """, - LanguageVersion = LanguageVersion.CSharp12, + LanguageVersion = LanguageVersion.Preview, }.RunAsync(); } @@ -5363,6 +5363,60 @@ 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/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 002b0a25e6cb1..48a9ac8a41427 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -310,8 +310,19 @@ private static bool RequiresRefOrOut(BindValueKind kind) #nullable enable - private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + var useSetAccessor = valueKind == BindValueKind.Assignable && !indexerAccess.Indexer.ReturnsByRef; var accessorForDefaultArguments = useSetAccessor ? indexerAccess.Indexer.GetOwnOrInheritedSetMethod() @@ -404,15 +415,26 @@ private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundI /// method returns a BoundBadExpression node. The method returns the original /// expression without generating any error if the expression has errors. /// - private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + switch (expr.Kind) { case BoundKind.PropertyGroup: expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics); if (expr is BoundIndexerAccess indexerAccess) { - expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); } break; @@ -430,7 +452,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind return expr; case BoundKind.IndexerAccess: - expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); break; case BoundKind.UnconvertedObjectCreationExpression: diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 0e9c9f8b4bcb7..9f8e70e818d03 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -128,7 +128,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( var type = boundRHS.Type ?? voidType; return new BoundDeconstructionAssignmentOperator( node, - DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true), + DeconstructionVariablesAsTuple(left, checkedVariables, assignmentResultTupleType: out _, diagnostics, ignoreDiagnosticsFromTuple: true), new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: null, type: type, hasErrors: true), resultIsUsed, @@ -154,9 +154,9 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( FailRemainingInferences(checkedVariables, diagnostics); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, out NamedTypeSymbol returnType, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); Debug.Assert(hasErrors || lhsTuple.Type is object); - TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type!; + returnType = hasErrors ? CreateErrorType() : returnType; var boundConversion = new BoundConversion( boundRHS.Syntax, @@ -316,8 +316,8 @@ private bool MakeDeconstructionConversion( } else { - var single = variable.Single; - Debug.Assert(single is object); + Debug.Assert(variable.Single is object); + var single = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _); Debug.Assert(single.Type is not null); CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); @@ -502,7 +502,7 @@ private string GetDebuggerDisplay() if ((object?)variable.Single.Type != null) { // typed-variable on the left - mergedType = variable.Single.Type; + mergedType = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _).Type; } } } @@ -542,11 +542,14 @@ private string GetDebuggerDisplay() syntax: syntax); } - private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables, + private BoundTupleExpression DeconstructionVariablesAsTuple( + CSharpSyntaxNode syntax, ArrayBuilder variables, + out NamedTypeSymbol assignmentResultTupleType, BindingDiagnosticBag diagnostics, bool ignoreDiagnosticsFromTuple) { int count = variables.Count; var valuesBuilder = ArrayBuilder.GetInstance(count); + var resultTypesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var typesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var locationsBuilder = ArrayBuilder.GetInstance(count); var namesBuilder = ArrayBuilder.GetInstance(count); @@ -554,18 +557,24 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn foreach (var variable in variables) { BoundExpression value; + TypeSymbol resultType; if (variable.NestedVariables is object) { - value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, ignoreDiagnosticsFromTuple); + value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, out NamedTypeSymbol nestedResultType, diagnostics, ignoreDiagnosticsFromTuple); + resultType = nestedResultType; namesBuilder.Add(null); } else { Debug.Assert(variable.Single is object); value = variable.Single; + Debug.Assert(value.Type is not null); + resultType = value.Type; + value = AdjustAssignmentTargetForDynamic(value, forceDynamicResult: out _); namesBuilder.Add(ExtractDeconstructResultElementName(value)); } valuesBuilder.Add(value); + resultTypesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(resultType)); typesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(value.Type)); locationsBuilder.Add(variable.Syntax.Location); } @@ -579,14 +588,39 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn ImmutableArray inferredPositions = tupleNames.IsDefault ? default : tupleNames.SelectAsArray(n => n != null); bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames(); + ImmutableArray elementLocations = locationsBuilder.ToImmutableAndFree(); + var createTupleDiagnostics = ignoreDiagnosticsFromTuple ? null : BindingDiagnosticBag.GetInstance(diagnostics); + var type = NamedTypeSymbol.CreateTuple( syntax.Location, - typesWithAnnotationsBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(), + typesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, + tupleNames, this.Compilation, + shouldCheckConstraints: createTupleDiagnostics is not null, + includeNullability: false, + errorPositions: disallowInferredNames ? inferredPositions : default, + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is { AccumulatesDiagnostics: true, DiagnosticBag: { } bag } && + bag.HasAnyResolvedErrors()) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + createTupleDiagnostics = null; // Suppress possibly duplicate errors from CreateTuple call below. + } + + // This type is the same as the 'type' above, or differs only by using 'dynamic' for some elements. + assignmentResultTupleType = NamedTypeSymbol.CreateTuple( + syntax.Location, + resultTypesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, tupleNames, this.Compilation, - shouldCheckConstraints: !ignoreDiagnosticsFromTuple, + shouldCheckConstraints: createTupleDiagnostics is not null, includeNullability: false, errorPositions: disallowInferredNames ? inferredPositions : default, - syntax: syntax, diagnostics: ignoreDiagnosticsFromTuple ? null : diagnostics); + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is not null) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + } return (BoundTupleExpression)BindToNaturalType(new BoundTupleLiteral(syntax, arguments, tupleNames, inferredPositions, type), diagnostics); } @@ -788,12 +822,17 @@ private DeconstructionVariable BindDeconstructionVariables( } default: var boundVariable = BindExpression(node, diagnostics, invoked: false, indexed: false); - var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics); + var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + if (expression == null && checkedVariable.Kind != BoundKind.DiscardExpression) { expression = node; } + // This object doesn't escape BindDeconstruction method, we don't call AdjustAssignmentTargetForDynamic + // for checkedVariable here, instead we call it where binder accesses DeconstructionVariable.Single + // In some of the places we need to be able to detect the fact that the type used to be dynamic, and, + // if we erase the fact here, there will be no other place for us to look at. return new DeconstructionVariable(checkedVariable, node); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index ce72e6c512004..d99058f28b208 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -233,10 +233,10 @@ internal NamedTypeSymbol CreateErrorType(string name = "") /// did not meet the requirements, the return value will be a that /// (typically) wraps the subexpression. /// - internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind) + internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind, bool dynamificationOfAssignmentResultIsHandled = false) { var result = this.BindExpression(node, diagnostics: diagnostics, invoked: false, indexed: false); - return CheckValue(result, valueKind, diagnostics); + return CheckValue(result, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled); } internal BoundExpression BindRValueWithoutTargetType(ExpressionSyntax node, BindingDiagnosticBag diagnostics, bool reportNoTargetType = true) @@ -5703,7 +5703,7 @@ private BoundExpression BindObjectInitializerMember( { // D = { ..., = , ... }, where D : dynamic boundMember = new BoundDynamicObjectInitializerMember(leftSyntax, memberName.Identifier.Text, implicitReceiver.Type, initializerType, hasErrors: false); - return CheckValue(boundMember, valueKind, diagnostics); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); } else { @@ -5806,7 +5806,15 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.IndexerAccess: { - var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics); + var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + + Debug.Assert(valueKind is BindValueKind.RValue or BindValueKind.RefAssignable or BindValueKind.Assignable); + + if (valueKind == BindValueKind.Assignable) + { + indexer = (BoundIndexerAccess)AdjustAssignmentTargetForDynamic(indexer, forceDynamicResult: out _); + } + boundMember = indexer; hasErrors |= isRhsNestedInitializer && !CheckNestedObjectInitializerPropertySymbol(indexer.Indexer, leftSyntax, diagnostics, hasErrors, ref resultKind); arguments = indexer.Arguments; @@ -5846,7 +5854,10 @@ private BoundExpression BindObjectInitializerMember( hasErrors |= !CheckNestedObjectInitializerPropertySymbol(property, leftSyntax, diagnostics, hasErrors, ref resultKind); } - return hasErrors ? boundMember : CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return hasErrors ? + boundMember : + CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); case BoundKind.DynamicObjectInitializerMember: break; @@ -5863,7 +5874,8 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.ArrayAccess: case BoundKind.PointerElementAccess: - return CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); default: return BadObjectInitializerMemberAccess(boundMember, implicitReceiver, leftSyntax, diagnostics, valueKind, hasErrors); @@ -5948,7 +5960,8 @@ private BoundExpression BadObjectInitializerMemberAccess( break; case LookupResultKind.Inaccessible: - boundMember = CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + boundMember = CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); Debug.Assert(boundMember.HasAnyErrors); break; @@ -9719,13 +9732,14 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( argumentSyntax, singleCandidate); } } - else + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(finalApplicableCandidates[0])) { var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); resultWithSingleCandidate.ResultsBuilder.Add(finalApplicableCandidates[0]); overloadResolutionResult.Free(); - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, hasDynamicArgument: true, diagnostics); } } @@ -9741,7 +9755,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( return BindDynamicIndexer(syntax, receiver, analyzedArguments, finalApplicableCandidates.SelectAsArray(r => r.Member), diagnostics); } - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, hasDynamicArgument: false, diagnostics); } private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( @@ -9750,6 +9764,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( ArrayBuilder propertyGroup, AnalyzedArguments analyzedArguments, OverloadResolutionResult overloadResolutionResult, + bool hasDynamicArgument, BindingDiagnosticBag diagnostics) { BoundExpression propertyAccess; @@ -9811,6 +9826,25 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( MemberResolutionResult resolutionResult = overloadResolutionResult.ValidResult; PropertySymbol property = resolutionResult.Member; + bool forceDynamicResultType = false; + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(property.Type.IsDynamic() || property.ReturnsByRef || + !Conversions.ClassifyConversionFromExpressionType(property.Type, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(resolutionResult))) + { + var tryDynamicAccessDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicIndexer(syntax, receiver, analyzedArguments, ImmutableArray.Create(property), tryDynamicAccessDiagnostics); + forceDynamicResultType = !tryDynamicAccessDiagnostics.HasAnyResolvedErrors(); + tryDynamicAccessDiagnostics.Free(); + } + + diagnostics.Add(syntax, useSiteInfo); + ReportDiagnosticsIfObsolete(diagnostics, property, syntax, hasBaseReceiver: receiver != null && receiver.Kind == BoundKind.BaseReference); // Make sure that the result of overload resolution is valid. @@ -9841,7 +9875,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( expanded: resolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm, argsToParams, defaultArguments: default, - property.Type, + forceDynamicResultType ? Compilation.DynamicType : property.Type, gotError); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 0f76849877b03..6e79c7c3f653c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -662,14 +662,19 @@ private BoundExpression BindDelegateInvocation( result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(applicable)) + { + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: true, boundExpression, diagnostics, queryClause); + } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: false, boundExpression, diagnostics, queryClause); } overloadResolutionResult.Free(); @@ -706,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, @@ -780,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. @@ -844,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); } } } @@ -999,23 +1011,33 @@ private BoundExpression TryEarlyBindSingleCandidateInvocationWithDynamicArgument return null; } - var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); - resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for local functions or expanded non-array params cases. + if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || + singleCandidate.MethodKind == MethodKind.LocalFunction || + IsMemberWithExpandedNonArrayParamsCollection(methodResolutionResult)) + { + var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); + resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); - BoundExpression result = BindInvocationExpressionContinued( - node: syntax, - expression: expression, - methodName: methodName, - result: resultWithSingleCandidate, - analyzedArguments: resolution.AnalyzedArguments, - methodGroup: resolution.MethodGroup, - delegateTypeOpt: null, - diagnostics: diagnostics, - queryClause: queryClause); + BoundExpression result = BindInvocationExpressionContinued( + node: syntax, + expression: expression, + methodName: methodName, + result: resultWithSingleCandidate, + analyzedArguments: resolution.AnalyzedArguments, + methodGroup: resolution.MethodGroup, + delegateTypeOpt: null, + hasDynamicArgument: true, + boundMethodGroup, + diagnostics: diagnostics, + queryClause: queryClause); - resultWithSingleCandidate.Free(); + resultWithSingleCandidate.Free(); - return result; + return result; + } + + return null; } private ImmutableArray> GetCandidatesPassingFinalValidation( @@ -1154,6 +1176,8 @@ private BoundCall BindInvocationExpressionContinued( AnalyzedArguments analyzedArguments, MethodGroup methodGroup, NamedTypeSymbol delegateTypeOpt, + bool hasDynamicArgument, + BoundExpression targetMethodGroupOrDelegateInstance, BindingDiagnosticBag diagnostics, CSharpSyntaxNode queryClause = null) { @@ -1229,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 @@ -1364,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 diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 40acd645365d9..1b22a794af71d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -22,7 +22,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, { node.Left.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind())); + BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind()), dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(left, diagnostics); BoundExpression right = BindValue(node.Right, diagnostics, BindValueKind.RValue); BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind()); @@ -43,6 +43,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, } } + left = AdjustAssignmentTargetForDynamic(left, out bool forceDynamicResult); + if (left.HasAnyErrors || right.HasAnyErrors) { // NOTE: no overload resolution candidates. @@ -81,7 +83,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, finalPlaceholder: placeholder, finalConversion: conversion, LookupResultKind.Viable, - left.Type, + AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasErrors: false); } else @@ -244,7 +246,56 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, var leftConversion = CreateConversion(node.Left, leftPlaceholder, best.LeftConversion, isCast: false, conversionGroupOpt: null, best.Signature.LeftType, diagnostics); return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted, - leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError); + leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasError); + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "reverting back to the indexer's type for the target" part. + /// See for the helper for the second part. + /// + private static BoundExpression AdjustAssignmentTargetForDynamic(BoundExpression target, out bool forceDynamicResult) + { + if (target is BoundIndexerAccess { Type.TypeKind: TypeKind.Dynamic, Indexer.Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + forceDynamicResult = true; + target = indexerAccess.Update( + indexerAccess.ReceiverOpt, + indexerAccess.InitialBindingReceiverIsSubjectToCloning, + indexerAccess.Indexer, + indexerAccess.Arguments, + indexerAccess.ArgumentNamesOpt, + indexerAccess.ArgumentRefKindsOpt, + indexerAccess.Expanded, + indexerAccess.ArgsToParamsOpt, + indexerAccess.DefaultArguments, + indexerAccess.Indexer.Type); + } + else + { + forceDynamicResult = false; + } + + return target; + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "setting result type of the assignment to 'dynamic' type" part. + /// See helper for the first part. + /// + TypeSymbol AdjustAssignmentTypeToDynamicIfNecessary(TypeSymbol leftType, bool forceDynamicResult) + { + return forceDynamicResult ? Compilation.DynamicType : leftType; } /// @@ -2261,7 +2312,9 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS { operandSyntax.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement), diagnostics); + BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement, dynamificationOfAssignmentResultIsHandled: true), + diagnostics); + UnaryOperatorKind kind = SyntaxKindToUnaryOperatorKind(node.Kind()); // If the operand is bad, avoid generating cascading errors. @@ -2283,6 +2336,8 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS hasErrors: true); } + operand = AdjustAssignmentTargetForDynamic(operand, out bool forceDynamicResult); + // The operand has to be a variable, property or indexer, so it must have a type. var operandType = operand.Type; Debug.Assert((object)operandType != null); @@ -2369,7 +2424,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS resultConversion, resultKind, originalUserDefinedOperators, - operandType, + AdjustAssignmentTypeToDynamicIfNecessary(operandType, forceDynamicResult), hasErrors); } @@ -4134,8 +4189,11 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node.OperatorToken); - BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment); + BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(leftOperand, diagnostics); + + leftOperand = AdjustAssignmentTargetForDynamic(leftOperand, out bool forceDynamicResult); + BoundExpression rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue); // If either operand is bad, bail out preventing more cascading errors @@ -4170,7 +4228,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { diagnostics.Add(node, useSiteInfo); var convertedRightOperand = CreateConversion(rightOperand, underlyingRightConversion, underlyingLeftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, underlyingLeftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(underlyingLeftType, forceDynamicResult)); + Debug.Assert(result.IsNullableValueTypeAssignment); + return result; } } @@ -4184,7 +4244,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio if (rightConversion.Exists) { var convertedRightOperand = CreateConversion(rightOperand, rightConversion, leftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, leftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(leftType, forceDynamicResult)); + Debug.Assert(!result.IsNullableValueTypeAssignment); + return result; } // a and b are incompatible and a compile-time error occurs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 23271214ea97a..5fbaa109f9bed 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1429,9 +1429,11 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD if (isRef) MessageID.IDS_FeatureRefReassignment.CheckFeatureAvailability(diagnostics, node.Right.GetFirstToken()); - var op1 = BindValue(node.Left, diagnostics, lhsKind); + var op1 = BindValue(node.Left, diagnostics, lhsKind, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(op1, diagnostics); + op1 = AdjustAssignmentTargetForDynamic(op1, out bool forceDynamicResult); + var rhsKind = isRef ? GetRequiredRHSValueKindForRefAssignment(op1) : BindValueKind.RValue; var op2 = BindValue(rhsExpr, diagnostics, rhsKind); @@ -1442,7 +1444,10 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD op1 = InferTypeForDiscardAssignment((BoundDiscardExpression)op1, op2, diagnostics); } - return BindAssignment(node, op1, op2, isRef, diagnostics); + BoundAssignmentOperator result = BindAssignment(node, op1, op2, isRef, diagnostics); + result = result.Update(result.Left, result.Right, result.IsRef, AdjustAssignmentTypeToDynamicIfNecessary(result.Type, forceDynamicResult)); + + return result; } private static BindValueKind GetRequiredRHSValueKindForRefAssignment(BoundExpression boundLeft) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index df844fd007afa..4a54eece1b5e2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -3847,9 +3847,10 @@ private static void GetSymbolsAndResultKind(BoundIncrementOperator increment, ou { Debug.Assert((object)increment.MethodOpt == null && increment.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty); UnaryOperatorKind op = increment.OperatorKind.Operator(); - symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(increment.Operand.Type.StrippedType(), + TypeSymbol opType = increment.Operand.Type.StrippedType(); + symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(opType, OperatorFacts.UnaryOperatorNameFromOperatorKind(op, isChecked: increment.OperatorKind.IsChecked()), - increment.Type.StrippedType())); + opType)); resultKind = increment.ResultKind; } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index bf4e997c62729..eb65524243bd8 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4081,7 +4081,8 @@ void setAnalyzedNullabilityAsContinuation( if (symbol != null) { - Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); + Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes) || + (symbol is PropertySymbol { IsIndexer: true } && objectInitializer.Type.IsDynamic())); symbol = AsMemberOfType(containingType, symbol); } @@ -5440,20 +5441,35 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly LearnFromNonNullTest(leftOperand, ref leftState); LearnFromNullTest(leftOperand, ref this.State); + var adjustedNodeType = node.Type; + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(node, leftOperand)) + { + Debug.Assert(leftOperand.Type is not null); + + if (node.IsNullableValueTypeAssignment) + { + adjustedNodeType = leftOperand.Type.GetNullableUnderlyingType(); + } + else + { + adjustedNodeType = leftOperand.Type; + } + } + // If we are assigning to a nullable value type variable, set the top-level state of // the LHS first, then change the slot to the Value property of the LHS to simulate // assignment of the RHS and update the nullable state of the underlying value type. if (node.IsNullableValueTypeAssignment) { Debug.Assert(targetType.Type.ContainsErrorType() || - node.Type?.ContainsErrorType() == true || - TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), node.Type, TypeCompareKind.AllIgnoreOptions)); + adjustedNodeType?.ContainsErrorType() == true || + TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), adjustedNodeType, TypeCompareKind.AllIgnoreOptions)); if (leftSlot > 0) { SetState(ref this.State, leftSlot, NullableFlowState.NotNull); leftSlot = GetNullableOfTValueSlot(targetType.Type, leftSlot, out _); } - targetType = TypeWithAnnotations.Create(node.Type, NullableAnnotation.NotAnnotated); + targetType = TypeWithAnnotations.Create(adjustedNodeType, NullableAnnotation.NotAnnotated); } TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand), trackMembers: false, AssignmentKind.Assignment); @@ -5462,10 +5478,49 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly Join(ref this.State, ref leftState); TypeWithState resultType = TypeWithState.Create(targetType.Type, rightResult.State); - SetResultType(node, resultType); + + if (adjustedNodeType != (object?)node.Type) + { + Debug.Assert(adjustedNodeType is not null); + SetDynamicResult(node, resultType); + } + else + { + SetResultType(node, resultType); + } + return null; } + /// + /// When an operation on an indexer with dynamic argument is resolved statically, + /// in some scenarios result type of the operation is set to 'dynamic' type. + /// + /// This helper takes care of the setting result type to 'dynamic' type. + /// + private void SetDynamicResult(BoundExpression node, TypeWithState sourceType) + { + Debug.Assert(node.Type is not null); + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(sourceType.Type is not null); + Debug.Assert(!sourceType.Type.IsDynamic()); + Debug.Assert(!sourceType.Type.IsVoidType()); + + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + + SetResultType(node, + VisitConversion( + conversionOpt: null, + conversionOperand: node, + _conversions.ClassifyConversionFromExpressionType(sourceType.Type, node.Type, isChecked: false, ref discardedUseSiteInfo), + targetTypeWithNullability: TypeWithAnnotations.Create(node.Type, NullableAnnotation.Annotated), + operandType: sourceType, + checkConversion: false, + fromExplicitCast: false, + useLegacyWarnings: false, + AssignmentKind.Assignment)); + } + public override BoundNode? VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { Debug.Assert(!IsConditionalState); @@ -6054,7 +6109,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr VisitResult? extensionReceiverResult = null; while (true) { - ReinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); + reinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); receiver = node; if (!calls.TryPop(out node!)) @@ -6090,7 +6145,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr else { TypeWithState receiverType = visitAndCheckReceiver(node); - ReinferMethodAndVisitArguments(node, receiverType); + reinferMethodAndVisitArguments(node, receiverType); } return null; @@ -6129,35 +6184,44 @@ TypeWithState visitAndCheckReceiver(BoundCall node) return receiverType; } - } - private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) - { - var method = node.Method; - ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; - if (!receiverType.HasNullType) + void reinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) { - // Update method based on inferred receiver type. - method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); - } + var method = node.Method; + ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; + if (!receiverType.HasNullType) + { + // Update method based on inferred receiver type. + method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); + } - ImmutableArray results; - bool returnNotNull; - (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, - node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); + ImmutableArray results; + bool returnNotNull; + (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, + node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); - ApplyMemberPostConditions(node.ReceiverOpt, method); + ApplyMemberPostConditions(node.ReceiverOpt, method); - LearnFromEqualsMethod(method, node, receiverType, results); + LearnFromEqualsMethod(method, node, receiverType, results); - var returnState = GetReturnTypeWithState(method); - if (returnNotNull) - { - returnState = returnState.WithNotNullState(); - } + var returnState = GetReturnTypeWithState(method); + if (returnNotNull) + { + returnState = returnState.WithNotNullState(); + } + + if (node.Type.IsDynamic() && !node.Method.ReturnType.IsDynamic()) + { + Debug.Assert(!node.Method.ReturnsByRef); + SetDynamicResult(node, returnState); + } + else + { + SetResult(node, returnState, method.ReturnTypeWithAnnotations); + } - SetResult(node, returnState, method.ReturnTypeWithAnnotations); - SetUpdatedSymbol(node, node.Method, method); + SetUpdatedSymbol(node, node.Method, method); + } } private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray results) @@ -9778,7 +9842,7 @@ private void VisitThisOrBaseReference(BoundExpression node) } else { - SetResult(node, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); + SetResultConveringToDynamicIfNecessary(node, left, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); } AdjustSetValue(left, ref rightState); @@ -9788,6 +9852,21 @@ private void VisitThisOrBaseReference(BoundExpression node) return null; } + private void SetResultConveringToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression assignmentTarget, TypeWithState resultType, TypeWithAnnotations lvalueType) + { + Debug.Assert(originalAssignment.Type is not null); + Debug.Assert(assignmentTarget.Type is not null); + + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(originalAssignment, assignmentTarget)) + { + SetDynamicResult(originalAssignment, resultType); + } + else + { + SetResult(originalAssignment, resultType, lvalueType); + } + } + private bool IsPropertyOutputMoreStrictThanInput(PropertySymbol property) { var type = property.TypeWithAnnotations; @@ -10297,7 +10376,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress { var op = node.OperatorKind.Operator(); TypeWithState resultType = (op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement) ? resultOfIncrementType : operandType; - SetResultType(node, resultType); + SetResultConveringToDynamicIfNecessary(node, node.Operand, resultType, resultType.ToTypeWithAnnotations(compilation)); setResult = true; TrackNullableStateForAssignment(node, targetType: operandLvalue, targetSlot: MakeSlot(node.Operand), valueType: resultOfIncrementType); @@ -10365,7 +10444,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress // Handle `[DisallowNull]` on LHS operand (final assignment target). CheckDisallowedNullAssignment(resultTypeWithState, leftArgumentAnnotations, node.Syntax); - SetResultType(node, resultTypeWithState); + SetResultConveringToDynamicIfNecessary(node, node.Left, resultTypeWithState, resultTypeWithState.ToTypeWithAnnotations(compilation)); AdjustSetValue(node.Left, ref resultTypeWithState); Debug.Assert(MakeSlot(node) == -1); @@ -10503,7 +10582,17 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter VisitArguments(node, node.Arguments, node.ArgumentRefKindsOpt, indexer, node.ArgsToParamsOpt, node.DefaultArguments, node.Expanded); var resultType = ApplyUnconditionalAnnotations(indexer.TypeWithAnnotations.ToTypeWithState(), GetRValueAnnotations(indexer)); - SetResult(node, resultType, indexer.TypeWithAnnotations); + + if (node.Type.IsDynamic() && !node.Indexer.Type.IsDynamic()) + { + Debug.Assert(!node.Indexer.ReturnsByRef); + SetDynamicResult(node, resultType); + } + else + { + SetResult(node, resultType, indexer.TypeWithAnnotations); + } + SetUpdatedSymbol(node, node.Indexer, indexer); return null; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index ee4a1091d7b32..9fcab11c2c2f6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -81,14 +81,56 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo break; } - return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, node.Type, used); + var result = MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, left, result, used); + + Debug.Assert(used || result.Type?.IsVoidType() == true || + (left switch { BoundIndexerAccess indexer => indexer.Indexer, BoundPropertyAccess property => property.PropertySymbol, _ => null }) is not PropertySymbol prop || + prop.GetOwnOrInheritedSetMethod() is null); + + Debug.Assert(result.Type?.IsVoidType() == true || TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); + + return result; + } + + private static bool ShouldConvertResultOfAssignmentToDynamic(TypeSymbol? assignmentResultType, BoundExpression target) + { + if (assignmentResultType?.IsDynamic() == true && target is BoundIndexerAccess { Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.Type.IsDynamic()); + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + + return true; + } + + return false; + } + + internal static bool ShouldConvertResultOfAssignmentToDynamic(BoundExpression assignment, BoundExpression target) + { + Debug.Assert(assignment is BoundAssignmentOperator or BoundIncrementOperator or BoundCompoundAssignmentOperator or BoundNullCoalescingAssignmentOperator); + return ShouldConvertResultOfAssignmentToDynamic(assignment.Type, target); + } + + private BoundExpression ConvertResultOfAssignmentToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression originalTarget, BoundExpression result, bool used) + { + Debug.Assert(originalAssignment.Type is not null); + if (used && ShouldConvertResultOfAssignmentToDynamic(originalAssignment, originalTarget)) + { + Debug.Assert(result.Type is not null); + Debug.Assert(!result.Type.IsDynamic()); + result = _factory.Convert(originalAssignment.Type, result); + } + + return result; } /// /// Generates a lowered form of the assignment operator for the given left and right sub-expressions. /// Left and right sub-expressions must be in lowered form. /// - private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, TypeSymbol type, + private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool used, bool isChecked, bool isCompoundAssignment) { switch (rewrittenLeft.Kind) @@ -132,7 +174,7 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio throw ExceptionUtilities.Unreachable(); default: - return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, type: type, used: used); + return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, used: used); } } @@ -168,7 +210,6 @@ private BoundExpression MakeStaticAssignmentOperator( BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool isRef, - TypeSymbol type, bool used) { switch (rewrittenLeft.Kind) @@ -193,7 +234,6 @@ private BoundExpression MakeStaticAssignmentOperator( false, default(ImmutableArray), rewrittenRight, - type, used); } @@ -214,7 +254,6 @@ private BoundExpression MakeStaticAssignmentOperator( indexerAccess.Expanded, indexerAccess.ArgsToParamsOpt, rewrittenRight, - type, used); } @@ -227,7 +266,6 @@ private BoundExpression MakeStaticAssignmentOperator( syntax, rewrittenLeft, rewrittenRight, - type, isRef); } @@ -252,9 +290,8 @@ private BoundExpression MakeStaticAssignmentOperator( sequence.Value, rewrittenRight, isRef, - type, used), - type); + sequence.Type); } goto default; @@ -264,8 +301,7 @@ private BoundExpression MakeStaticAssignmentOperator( return _factory.AssignmentExpression( syntax, rewrittenLeft, - rewrittenRight, - type); + rewrittenRight); } } } @@ -279,7 +315,6 @@ private BoundExpression MakePropertyAssignment( bool expanded, ImmutableArray argsToParamsOpt, BoundExpression rewrittenRight, - TypeSymbol type, bool used) { // Rewrite property assignment into call to setter. @@ -350,7 +385,7 @@ private BoundExpression MakePropertyAssignment( AppendToPossibleNull(argTemps, rhsTemp), ImmutableArray.Create(setterCall), boundRhs, - type); + rhsTemp.Type); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 7e7c8d253ac70..956ff4e6071c1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -410,13 +410,23 @@ BoundExpression visitArgumentsAndFinishRewrite(BoundCall node, BoundExpression? Instrumenter.InterceptCallAndAdjustArguments(ref method, ref rewrittenReceiver, ref rewrittenArguments, ref argRefKindsOpt); } - var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, node.Type, temps.ToImmutableAndFree()); + var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, temps.ToImmutableAndFree()); if (Instrument) { rewrittenCall = Instrumenter.InstrumentCall(node, rewrittenCall); } + if (node.Type.IsDynamic() && !method.ReturnType.IsDynamic()) + { + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(!method.ReturnsByRef); + Debug.Assert(rewrittenCall.Type is not null); + Debug.Assert(!rewrittenCall.Type.IsDynamic()); + Debug.Assert(!rewrittenCall.Type.IsVoidType()); + rewrittenCall = _factory.Convert(node.Type, rewrittenCall); + } + return rewrittenCall; } } @@ -429,7 +439,6 @@ private BoundExpression MakeCall( ImmutableArray rewrittenArguments, ImmutableArray argumentRefKinds, LookupResultKind resultKind, - TypeSymbol type, ImmutableArray temps) { BoundExpression rewrittenBoundCall; @@ -454,7 +463,7 @@ private BoundExpression MakeCall( resultKind, rewrittenArguments[0], rewrittenArguments[1], - type); + method.ReturnType); } else if (node == null) { @@ -472,7 +481,7 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), resultKind: resultKind, - type: type); + type: method.ReturnType); } else { @@ -489,9 +498,11 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), node.ResultKind, - node.Type); + method.ReturnType); } + Debug.Assert(rewrittenBoundCall.Type is not null); + if (!temps.IsDefaultOrEmpty) { return new BoundSequence( @@ -499,13 +510,13 @@ private BoundExpression MakeCall( locals: temps, sideEffects: ImmutableArray.Empty, value: rewrittenBoundCall, - type: type); + type: rewrittenBoundCall.Type); } return rewrittenBoundCall; } - private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments, TypeSymbol type) + private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments) { return MakeCall( node: null, @@ -515,7 +526,6 @@ private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenRe rewrittenArguments: rewrittenArguments, argumentRefKinds: default(ImmutableArray), resultKind: LookupResultKind.Viable, - type: type, temps: default); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index c015096e3b21c..ed3c55336a5d1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -113,6 +113,10 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO temps.Free(); stores.Free(); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Left, result, used); + + Debug.Assert(used || node.Left is not (BoundIndexerAccess { Indexer.RefKind: RefKind.None } or BoundPropertyAccess { PropertySymbol.RefKind: RefKind.None }) || result.Type?.IsVoidType() == true); return result; BoundExpression rewriteAssignment(BoundExpression leftRead) @@ -153,7 +157,8 @@ BoundExpression rewriteAssignment(BoundExpression leftRead) RemovePlaceholderReplacement(node.FinalPlaceholder); } - return MakeAssignmentOperator(syntax, transformedLHS, opFinal, node.Left.Type, used: used, isChecked: isChecked, isCompoundAssignment: true); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.Left.Type, TypeCompareKind.AllIgnoreOptions)); + return MakeAssignmentOperator(syntax, transformedLHS, opFinal, used: used, isChecked: isChecked, isCompoundAssignment: true); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 635043ef290bd..4f6b4f96c378d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -1421,9 +1421,6 @@ private BoundExpression RewriteIntPtrConversion( rewrittenOperand = MakeConversionNode(rewrittenOperand, method.GetParameterType(0), @checked); - var returnType = method.ReturnType; - Debug.Assert((object)returnType != null); - if (_inExpressionLambda) { return BoundConversion.Synthesized(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode: explicitCastInCode, conversionGroupOpt: null, constantValueOpt, rewrittenType); @@ -1433,8 +1430,7 @@ private BoundExpression RewriteIntPtrConversion( syntax: syntax, rewrittenReceiver: null, method: method, - rewrittenArguments: ImmutableArray.Create(rewrittenOperand), - type: returnType); + rewrittenArguments: ImmutableArray.Create(rewrittenOperand)); return MakeConversionNode(rewrittenCall, rewrittenType, @checked, markAsChecked: true); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index dfbe8f214ae22..bb24b2810febb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -18,8 +18,8 @@ internal sealed partial class LocalRewriter { var right = node.Right; Debug.Assert(right.Conversion.Kind == ConversionKind.Deconstruction); - - return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed); + Debug.Assert(node.Type.IsTupleType); + return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed, (NamedTypeSymbol)node.Type); } /// @@ -34,13 +34,12 @@ internal sealed partial class LocalRewriter /// - the conversion phase /// - the assignment phase /// - private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed) + private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed, NamedTypeSymbol assignmentResultTupleType) { var lhsTemps = ArrayBuilder.GetInstance(); var lhsEffects = ArrayBuilder.GetInstance(); ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects); - Debug.Assert(left.Type is { }); - BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed); + BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, right, assignmentResultTupleType, isUsed); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); if (result is null) { @@ -55,8 +54,8 @@ internal sealed partial class LocalRewriter private BoundExpression? RewriteDeconstruction( ArrayBuilder lhsTargets, Conversion conversion, - TypeSymbol leftType, BoundExpression right, + NamedTypeSymbol assignmentResultTupleType, bool isUsed) { if (right.Kind == BoundKind.ConditionalOperator) @@ -66,17 +65,17 @@ internal sealed partial class LocalRewriter return conditional.Update( conditional.IsRef, VisitExpression(conditional.Condition), - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true)!, - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Consequence, assignmentResultTupleType, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Alternative, assignmentResultTupleType, isUsed: true)!, conditional.ConstantValueOpt, - leftType, + assignmentResultTupleType, wasTargetTyped: true, - leftType); + assignmentResultTupleType); } var temps = ArrayBuilder.GetInstance(); var effects = DeconstructionSideEffects.GetInstance(); - BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true); + BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, assignmentResultTupleType, isUsed, inInit: true); reverseAssignmentsToTargetsIfApplicable(); effects.Consolidate(); @@ -213,6 +212,7 @@ static bool canReorderTargetAssignments(ArrayBuilder temps, DeconstructionSideEffects effects, + NamedTypeSymbol assignmentResultTupleType, bool isUsed, bool inInit) { @@ -227,14 +227,19 @@ static bool canReorderTargetAssignments(ArrayBuilder(removeDelegate), - type: clearMethod.ReturnType); + rewrittenArguments: ImmutableArray.Create(removeDelegate)); } else { @@ -163,8 +163,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode syntax: syntax, rewrittenReceiver: null, method: marshalMethod, - rewrittenArguments: marshalArguments, - type: marshalMethod.ReturnType); + rewrittenArguments: marshalArguments); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index 2009b7fb09125..d69554e9b960d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -91,7 +91,6 @@ private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftO node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, - node.Type, node, isLeftOfAssignment); } @@ -106,17 +105,22 @@ private BoundExpression MakeIndexerAccess( bool expanded, ImmutableArray argsToParamsOpt, BitVector defaultArguments, - TypeSymbol type, - BoundIndexerAccess? oldNodeOpt, + BoundExpression originalIndexerAccessOrObjectInitializerMember, bool isLeftOfAssignment) { + Debug.Assert(originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess or BoundObjectInitializerMember); + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type is not null); + if (isLeftOfAssignment && indexer.RefKind == RefKind.None) { + TypeSymbol type = indexer.Type; + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type.Equals(type, TypeCompareKind.ConsiderEverything)); + // This is an indexer set access. We return a BoundIndexerAccess node here. // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. - return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : + return originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess indexerAccess ? + indexerAccess.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : new BoundIndexerAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type); } else @@ -145,6 +149,16 @@ private BoundExpression MakeIndexerAccess( BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentRefKindsOpt, getMethod); + if (originalIndexerAccessOrObjectInitializerMember.Type.IsDynamic() == true && !indexer.Type.IsDynamic()) + { + Debug.Assert(call.Type is not null); + Debug.Assert(!call.Type.IsDynamic()); + Debug.Assert(!getMethod.ReturnsByRef); + call = _factory.Convert(originalIndexerAccessOrObjectInitializerMember.Type, call); + } + + Debug.Assert(call.Type is not null); + if (temps.Count == 0) { temps.Free(); @@ -157,7 +171,7 @@ private BoundExpression MakeIndexerAccess( temps.ToImmutableAndFree(), ImmutableArray.Empty, call, - type); + call.Type); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs index b2d42a9a8597e..7e24d7df4caa4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs @@ -63,7 +63,6 @@ internal sealed partial class LocalRewriter localSymbol.Type ), rewrittenInitializer, - localSymbol.Type, localSymbol.IsRef), hasErrors); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs index 851da6ffc4147..6b3b62aa0bc01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs @@ -25,10 +25,12 @@ public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalesc var lhsRead = MakeRValue(transformedLHS); BoundExpression loweredRight = VisitExpression(node.RightOperand); - return node.IsNullableValueTypeAssignment ? + var result = node.IsNullableValueTypeAssignment ? rewriteNullCoalescingAssignmentForValueType() : rewriteNullCoalscingAssignmentStandard(); + return ConvertResultOfAssignmentToDynamicIfNecessary(node, node.LeftOperand, result, used: true); + BoundExpression rewriteNullCoalscingAssignmentStandard() { // Now that LHS is transformed with temporaries, we rewrite this node into a coalesce expression: @@ -38,7 +40,8 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() // isCompoundAssignment is only used for dynamic scenarios, and we want those scenarios to treat this like a standard assignment. // See CodeGenNullCoalescingAssignmentTests.CoalescingAssignment_DynamicRuntimeCastFailure, which will fail if // isCompoundAssignment is set to true. It will fail to throw a runtime binder cast exception. - BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); + BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); // lhsRead ?? (transformedLHS = loweredRight) var leftPlaceholder = new BoundValuePlaceholder(lhsRead.Syntax, lhsRead.Type); @@ -60,7 +63,6 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() BoundExpression rewriteNullCoalescingAssignmentForValueType() { Debug.Assert(node.LeftOperand.Type.IsNullableType()); - Debug.Assert(node.Type.Equals(node.RightOperand.Type)); // We lower the expression to this form: // @@ -107,17 +109,17 @@ BoundExpression rewriteNullCoalescingAssignmentForValueType() temps.Add(tmp.LocalSymbol); // tmp = loweredRight; - var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, node.Type, used: true, isChecked: false, isCompoundAssignment: false); + var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); Debug.Assert(transformedLHS.Type.GetNullableUnderlyingType().Equals(tmp.Type.StrippedType(), TypeCompareKind.AllIgnoreOptions)); // transformedLhs = tmp; + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); var transformedLhsAssignment = MakeAssignmentOperator( node.Syntax, transformedLHS, MakeConversionNode(tmp, transformedLHS.Type, @checked: false, markAsChecked: true), - node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index 5ca410e45cde8..8583c6cf6329a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -206,7 +206,7 @@ private BoundExpression MakeDynamicCollectionInitializer(BoundExpression rewritt Instrumenter.InterceptCallAndAdjustArguments(ref addMethod, ref rewrittenReceiver, ref rewrittenArguments, ref argumentRefKindsOpt); } - return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, temps.ToImmutableAndFree()); } private BoundExpression VisitObjectInitializerMember(BoundObjectInitializerMember node, ref BoundExpression rewrittenReceiver, ArrayBuilder sideEffects, ref ArrayBuilder? temps) @@ -363,7 +363,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, assignment.Type, used: false)); + Debug.Assert(assignment.Type.IsDynamic() || TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, used: false)); return; } } @@ -434,7 +435,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -466,7 +468,8 @@ private void AddObjectInitializer( { // Rewrite as simple assignment. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -499,7 +502,8 @@ private void AddObjectInitializer( if (!isRhsNestedInitializer) { var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, used: false)); return; } @@ -684,8 +688,7 @@ private BoundExpression MakeObjectInitializerMemberAccess( rewrittenLeft.Expanded, rewrittenLeft.ArgsToParamsOpt, rewrittenLeft.DefaultArguments, - type: propertySymbol.Type, - oldNodeOpt: null, + originalIndexerAccessOrObjectInitializerMember: rewrittenLeft, isLeftOfAssignment: !isRhsNestedInitializer); } else diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 5e3384761eba7..00d6f2d7854b0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -438,7 +438,8 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamic); TypeSymbol? operandType = transformedLHS.Type; //type of the variable being incremented Debug.Assert(operandType is { }); - Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2)); + Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2) || + ShouldConvertResultOfAssignmentToDynamic(node, node.Operand)); LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType); tempSymbols.Add(tempSymbol); @@ -452,7 +453,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // prefix: (X)(T.Increment((T)operand))) // postfix: (X)(T.Increment((T)temp))) - var newValue = MakeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); + var newValue = makeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); // there are two strategies for completing the rewrite. // The reason is that indirect assignments read the target of the assignment before evaluating @@ -472,122 +473,131 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // In a case of the non-byref operand we use a single-sequence strategy as it results in shorter // overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case) // - if (IsIndirectOrInstanceField(transformedLHS)) + BoundExpression result; + + if (isIndirectOrInstanceField(transformedLHS)) { - return RewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } else { - return RewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } - } - - private static bool IsIndirectOrInstanceField(BoundExpression expression) - { - switch (expression.Kind) - { - case BoundKind.Local: - return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - case BoundKind.Parameter: - Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); - return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Operand, result, used: true); + Debug.Assert(TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); - case BoundKind.FieldAccess: - return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; - } + return result; - return false; - } + static bool isIndirectOrInstanceField(BoundExpression expression) + { + switch (expression.Kind) + { + case BoundKind.Local: + return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithNotRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression transformedLHS, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; - // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); - ImmutableArray assignments = ImmutableArray.Create( - MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), operandType, used: false, isChecked: isChecked, isCompoundAssignment: false), - MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false)); - - // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) - // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), - value: boundTemp, - type: operandType); - } + case BoundKind.Parameter: + Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); + return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression operand, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - var tempValue = isPrefix ? newValue : MakeRValue(operand); - Debug.Assert(tempValue.Type is { }); - var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); + case BoundKind.FieldAccess: + return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; + } - var operandValue = isPrefix ? boundTemp : newValue; - var tempAssignedAndOperandValue = new BoundSequence( - syntax, - ImmutableArray.Empty, - ImmutableArray.Create(tempAssignment), - operandValue, - tempValue.Type); - - // prefix: operand = Seq{temp = (T)(operand + 1); temp;} - // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} - BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); - - // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} - // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} - tempInitializers.Add(operandAssignment); - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree(), - value: boundTemp, - type: operandType); - } + return false; + } - private BoundExpression MakeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) - { - if (node.OperatorKind.IsDynamic()) + BoundExpression rewriteWithNotRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression transformedLHS, + BoundExpression boundTemp, + BoundExpression newValue) { - return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + Debug.Assert(boundTemp.Type is not null); + + // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; + // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); + ImmutableArray assignments = ImmutableArray.Create( + MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), used: false, isChecked: isChecked, isCompoundAssignment: false), + MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, used: false, isChecked: isChecked, isCompoundAssignment: false)); + + // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) + // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), + value: boundTemp, + type: boundTemp.Type); } - BoundExpression result; - if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + BoundExpression rewriteWithRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression operand, + BoundExpression boundTemp, + BoundExpression newValue) { - result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + Debug.Assert(boundTemp.Type is not null); + + var tempValue = isPrefix ? newValue : MakeRValue(operand); + Debug.Assert(tempValue.Type is { }); + var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + var operandValue = isPrefix ? boundTemp : newValue; + var tempAssignedAndOperandValue = new BoundSequence( + syntax, + ImmutableArray.Empty, + ImmutableArray.Create(tempAssignment), + operandValue, + tempValue.Type); + + // prefix: operand = Seq{temp = (T)(operand + 1); temp;} + // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} + BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} + // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} + tempInitializers.Add(operandAssignment); + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree(), + value: boundTemp, + type: boundTemp.Type); } - else + + BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { - result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); - } + if (node.OperatorKind.IsDynamic()) + { + return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + } - // Generate the conversion back to the type of the original expression. + BoundExpression result; + if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + { + result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + } + else + { + result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); + } - // (X)(short)((int)(short)x + 1) - result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + // Generate the conversion back to the type of the original expression. - return result; + // (X)(short)((int)(short)x + 1) + result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + + return result; + } } private BoundExpression ApplyConversionIfNotIdentity(BoundExpression? conversion, BoundValuePlaceholder? placeholder, BoundExpression replacement) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 4997351f94f68..249c7b23c0f1e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -532,7 +532,7 @@ private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo method ref temps, invokedAsExtensionMethod: method.IsExtensionMethod); - return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, method.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, temps.ToImmutableAndFree()); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index b94eddbaea342..ec3d3a6db7114 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -469,20 +469,20 @@ public BoundExpressionStatement ExpressionStatement(BoundExpression expr) /// public BoundExpression AssignmentExpression(BoundExpression left, BoundExpression right, bool isRef = false) { - Debug.Assert(left.Type is { } && right.Type is { } && - (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || - StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || - right.Type.IsErrorType() || left.Type.IsErrorType())); - - return AssignmentExpression(Syntax, left, right, left.Type, isRef: isRef, wasCompilerGenerated: true); + return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true); } /// /// Creates a general assignment that might be instrumented. /// - public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, TypeSymbol type, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) + public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) { - var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; + Debug.Assert(left.Type is { } && right.Type is { } && + (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || + StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || + right.Type.IsErrorType() || left.Type.IsErrorType())); + + var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, left.Type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; return (InstrumentationState?.IsSuppressed == false && left is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.UserDefined } or BoundParameter) ? InstrumentationState.Instrumenter.InstrumentUserDefinedLocalAssignment(assignment) : diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs index ebde2d102e21d..a382338ea9d86 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs @@ -15000,6 +15000,42 @@ static void M(bool result) VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72931")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void ObjectCreationFlow_75_CollectionInitializerError() + { + string source = @" +public class C +{ + public static void Main() + /**/{ + int d = 1; + var c = new C() { [d] = {2} }; + }/**/ + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + } +} + +class C2 +{ +} +"; + var expectedDiagnostics = new[] { + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + }; + + string expectedFlowGraph = @" +"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + [Fact] public void ObjectCreationExpression_NoNewConstraint() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index ea675e195a4cd..66f72c60a5364 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")] @@ -4960,5 +5062,6904 @@ public int this[string s] 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()); + + // IInvalidOperation is pre-existing condition - https://github.com/dotnet/roslyn/issues/72916 + var propertyRef = (IInvalidOperation)model.GetOperation(elementAccess); + //var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + //AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_ObjectInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref C2 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,35): error CS0117: 'C2' does not contain a definition for 'F' + // var c = new C() { [d] = { F = 2 } }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "F").WithArguments("C2", "F").WithLocation(7, 35) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [ConditionalFact(typeof(NoIOperationValidation))] // IOperation validation is suppressed due to https://github.com/dotnet/roslyn/issues/72931 + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_CollectionInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Collections.Generic.List C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Collections.Generic.List", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic)2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (""2"", 123); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,30): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = ("2", 123); + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(8, 30) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Tuple() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72913")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + comp3.VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72914")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp3.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int?'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int?").WithLocation(10, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out string x, out int y) + { + (x, y) = (""2"", 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c[d]").WithArguments("string", "int").WithLocation(8, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Method() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = ((2, 123), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + right = (TupleExpressionSyntax)right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = (new C2(), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(C2, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(C2, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Conditional() + { + string source = @" +public class C +{ + public static void Main() + { + Test(true); + System.Console.Write("" ""); + Test(false); + } + + static void Test(bool b) + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = b ? (2, 123) : (3, 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2 (3, 124) 3").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_01() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_02() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_Extension_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var model = comp1.GetSemanticModel(tree); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal(OperationKind.Invalid, model.GetOperation(call).Kind); + + comp1.VerifyDiagnostics( + // (7,22): error CS1973: 'C' has no applicable method named 'Test' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. + // var result = new C().Test("name", d); + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, @"new C().Test(""name"", d)").WithArguments("C", "Test").WithLocation(7, 22) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Delegate_CSharp12() + { + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_01() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_02() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index b31d4f8291d41..b4990d8031bfd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -3192,7 +3192,7 @@ static void M() TestOperatorKinds(code); } - private void TestBoundTree(string source, System.Func>, IEnumerable> query) + private static void TestBoundTree(string source, System.Func>, IEnumerable> query) { // The mechanism of this test is: we build the bound tree for the code passed in and then extract // from it the nodes that describe the operators. We then compare the description of @@ -3217,7 +3217,7 @@ private void TestBoundTree(string source, System.Func from edge in edges @@ -3260,7 +3260,7 @@ select string.Join(" ", from child in node.Children }))); } - private void TestTypes(string source) + internal static void TestTypes(string source) { TestBoundTree(source, edges => from edge in edges @@ -3289,7 +3289,7 @@ private static string FormatTypeArgumentList(ImmutableArray return s + ">"; } - private void TestDynamicMemberAccessCore(string source) + internal static void TestDynamicMemberAccessCore(string source) { TestBoundTree(source, edges => from edge in edges diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs index 882913c850da3..2e233219a3fc0 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs @@ -225,7 +225,7 @@ public void M(dynamic d) "; var semanticInfo = GetSemanticInfoForTest(sourceCode1); - Assert.Equal("C", semanticInfo.Type.ToTestDisplayString()); + Assert.True(semanticInfo.Type.IsDynamic()); Assert.Equal("C C.Create(System.Int32 arg)", semanticInfo.Symbol.ToTestDisplayString()); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); @@ -548,8 +548,8 @@ public int this[int a] "; var semanticInfo = GetSemanticInfoForTest(sourceCode); - Assert.False(semanticInfo.Type.IsDynamic()); - Assert.False(semanticInfo.ConvertedType.IsDynamic()); + Assert.True(semanticInfo.Type.IsDynamic()); + Assert.True(semanticInfo.ConvertedType.IsDynamic()); Assert.Equal(ConversionKind.Identity, semanticInfo.ImplicitConversion.Kind); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); From 5b3db93e908779572bb76d32bf2335c564992ea1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:13:43 -0700 Subject: [PATCH 0119/1047] Add asset hints --- .../Portable/Workspace/Solution/AssetPath.cs | 54 +++++++++---------- .../Remote/ServiceHub/Host/AssetProvider.cs | 34 ++++++------ 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index eb66c72c50268..06385bdfea8bf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -53,7 +53,7 @@ internal readonly struct AssetPath [DataMember(Order = 2)] public readonly DocumentId? DocumentId; - private AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? documentId = null) + public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? documentId = null) { _kind = kind; ProjectId = projectId; @@ -96,31 +96,31 @@ public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) /// public static AssetPath DocumentsInProject(ProjectId projectId) => new(AssetPathKind.Documents, projectId); +} - [Flags] - private enum AssetPathKind - { - /// - /// Search solution-level information. - /// - Solution = 1 << 0, - - ProjectChecksums = 1 << 1, - ProjectAttributes = 1 << 2, - ProjectCompilationOptions = 1 << 3, - ProjectParseOptions = 1 << 4, - ProjectProjectReferences = 1 << 5, - ProjectMetadataReferences = 1 << 6, - ProjectAnalyzerReferences = 1 << 7, - - /// - /// Search projects for results. All project-level information will be searched. - /// - Projects = ProjectChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, - - /// - /// Search documents for results. - /// - Documents = 1 << 8, - } +[Flags] +internal enum AssetPathKind +{ + /// + /// Search solution-level information. + /// + Solution = 1 << 0, + + ProjectChecksums = 1 << 1, + ProjectAttributes = 1 << 2, + ProjectCompilationOptions = 1 << 3, + ProjectParseOptions = 1 << 4, + ProjectProjectReferences = 1 << 5, + ProjectMetadataReferences = 1 << 6, + ProjectAnalyzerReferences = 1 << 7, + + /// + /// Search projects for results. All project-level information will be searched. + /// + Projects = ProjectChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, + + /// + /// Search documents for results. + /// + Documents = 1 << 8, } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 6b9cac223479d..1840914174f56 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -161,31 +161,32 @@ public async ValueTask SynchronizeProjectAssetsAsync( using var _ = ArrayBuilder.GetInstance(allProjectChecksums.Count, out var tasks); foreach (var singleProjectChecksums in allProjectChecksums) { - // We want to synchronize the assets just for this project. So we can pass the project down as a - // hint to limit the search on the host side. - AssetPath assetPath = singleProjectChecksums.ProjectId; - // 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); - tasks.Add(SynchronizeProjectAssetsWorkerAsync(tempBuffer, assetPath, freeArrayBuilder: true)); + // 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)); } 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. - var assetPath = AssetPath.SolutionAndProjects; - await SynchronizeProjectAssetsWorkerAsync(allProjectChecksums, assetPath, freeArrayBuilder: false).ConfigureAwait(false); + // 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).ConfigureAwait(false); } } async Task SynchronizeProjectAssetsWorkerAsync( - ArrayBuilder allProjectChecksums, AssetPath assetPath, bool freeArrayBuilder) + ArrayBuilder allProjectChecksums, ProjectId? projectId, bool freeArrayBuilder) { try { @@ -193,13 +194,14 @@ async Task SynchronizeProjectAssetsWorkerAsync( using var _ = ArrayBuilder.GetInstance(out var tasks); - // Make parallel requests for all the project data across all projects at once. - tasks.Add(SynchronizeProjectAssetAsync(assetPath, static p => p.Info)); - tasks.Add(SynchronizeProjectAssetAsync(assetPath, static p => p.CompilationOptions)); - tasks.Add(SynchronizeProjectAssetAsync(assetPath, static p => p.ParseOptions)); - tasks.Add(SynchronizeProjectAssetCollectionAsync(assetPath, static p => p.ProjectReferences)); - tasks.Add(SynchronizeProjectAssetCollectionAsync(assetPath, static p => p.MetadataReferences)); - tasks.Add(SynchronizeProjectAssetCollectionAsync(assetPath, static p => p.AnalyzerReferences)); + // 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) From 8929756e7049a4226d694e25ce5d853b088b7d55 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:14:33 -0700 Subject: [PATCH 0120/1047] REmove unused --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 6 ------ src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 06385bdfea8bf..d735c6ec6bcb3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -21,12 +21,6 @@ internal readonly struct AssetPath /// public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); - /// - /// Instance that will only look up solution-level, as well ProjectStateChecksums when searching for checksums. It - /// will not descend into any other project data, including not descending into documents. - /// - public static readonly AssetPath SolutionAndProjectChecksums = new(AssetPathKind.Solution | AssetPathKind.ProjectChecksums); - /// /// Only search at the project level when searching for checksums. /// diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 1840914174f56..e005904e72e76 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -112,7 +112,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() // and then the top level project-state-checksum values only. No other project data or document data will be // looked at. await this.SynchronizeAssetsAsync>( - AssetPath.SolutionAndProjectChecksums, + assetPath: new(AssetPathKind.Solution | AssetPathKind.ProjectChecksums), checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); From 38902cf8551f36101ea0be3fa6fb235773cbf9bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:15:43 -0700 Subject: [PATCH 0121/1047] Simplify more --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 9 ++------- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 6 +++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index d735c6ec6bcb3..5812662981327 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -21,11 +21,6 @@ internal readonly struct AssetPath /// public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); - /// - /// Only search at the project level when searching for checksums. - /// - public static readonly AssetPath ProjectsOnly = new(AssetPathKind.Projects); - /// /// 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. @@ -100,7 +95,7 @@ internal enum AssetPathKind /// Solution = 1 << 0, - ProjectChecksums = 1 << 1, + ProjectStateChecksums = 1 << 1, ProjectAttributes = 1 << 2, ProjectCompilationOptions = 1 << 3, ProjectParseOptions = 1 << 4, @@ -111,7 +106,7 @@ internal enum AssetPathKind /// /// Search projects for results. All project-level information will be searched. /// - Projects = ProjectChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, + Projects = ProjectStateChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, /// /// Search documents for results. diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 2dfe744c68a2e..bc13847daac4c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -220,7 +220,7 @@ private async Task UpdateProjectsAsync( newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.ProjectsOnly, newChecksumsToSync, + assetPath: new(AssetPathKind.ProjectStateChecksums), newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); @@ -253,14 +253,14 @@ private async Task UpdateProjectsAsync( projectItemChecksums.Add(newProjectChecksums.Info); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.ProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: new(AssetPathKind.ProjectAttributes), projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); projectItemChecksums.Clear(); foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) projectItemChecksums.Add(newProjectChecksums.CompilationOptions); await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.ProjectsOnly, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: new(AssetPathKind.ProjectCompilationOptions), projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); From 2a39c5c7722c1c85d16725ae28535cde94c89067 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:16:59 -0700 Subject: [PATCH 0122/1047] Add implicit --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 8 +++++--- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 5812662981327..de4ffc77fc316 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -19,13 +19,13 @@ internal readonly struct AssetPath /// /// Instance that will only look up solution-level data when searching for checksums. /// - public static readonly AssetPath SolutionOnly = new(AssetPathKind.Solution); + public static readonly AssetPath SolutionOnly = AssetPathKind.Solution; /// /// 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 = new(AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Documents); + public static readonly AssetPath FullLookupForTesting = AssetPathKind.Solution | AssetPathKind.Projects | AssetPathKind.Documents; [DataMember(Order = 0)] private readonly AssetPathKind _kind; @@ -52,7 +52,7 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do public bool IncludeSolution => (_kind & AssetPathKind.Solution) != 0; public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; - public bool IncludeProjectChecksums => (_kind & AssetPathKind.ProjectChecksums) != 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; @@ -60,6 +60,8 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do public bool IncludeProjectMetadataReferences => (_kind & AssetPathKind.ProjectMetadataReferences) != 0; public bool IncludeProjectAnalyzerReferences => (_kind & AssetPathKind.ProjectAnalyzerReferences) != 0; + public static implicit operator AssetPath(AssetPathKind kind) => new(kind); + /// /// Searches only for information about this project. /// diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index bc13847daac4c..d109b99dad76e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -220,7 +220,7 @@ private async Task UpdateProjectsAsync( newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); await _assetProvider.GetAssetsAsync>( - assetPath: new(AssetPathKind.ProjectStateChecksums), newChecksumsToSync, + assetPath: AssetPathKind.ProjectStateChecksums, newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { Contract.ThrowIfTrue(checksum != newProjectStateChecksum.Checksum); @@ -253,14 +253,14 @@ private async Task UpdateProjectsAsync( projectItemChecksums.Add(newProjectChecksums.Info); await _assetProvider.GetAssetsAsync( - assetPath: new(AssetPathKind.ProjectAttributes), projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.ProjectAttributes, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); projectItemChecksums.Clear(); foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) projectItemChecksums.Add(newProjectChecksums.CompilationOptions); await _assetProvider.GetAssetsAsync( - assetPath: new(AssetPathKind.ProjectCompilationOptions), projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.ProjectCompilationOptions, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); From 43040946d161be18f4412fefd1e4eb570484ec78 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:24:58 -0700 Subject: [PATCH 0123/1047] Use kinds uniformly --- .../Portable/Workspace/Solution/AssetPath.cs | 11 +++++++- .../Workspace/Solution/ChecksumCollection.cs | 7 ++--- .../Workspace/Solution/StateChecksums.cs | 26 +++++++------------ .../Remote/ServiceHub/Host/AssetProvider.cs | 4 +-- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index de4ffc77fc316..6001848ab5e0d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -52,6 +52,7 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do public bool IncludeSolution => (_kind & AssetPathKind.Solution) != 0; public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; + public bool IncludeProjectStateChecksums => (_kind & AssetPathKind.ProjectStateChecksums) != 0; public bool IncludeProjectAttributes => (_kind & AssetPathKind.ProjectAttributes) != 0; public bool IncludeProjectCompilationOptions => (_kind & AssetPathKind.ProjectCompilationOptions) != 0; @@ -60,6 +61,10 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do public bool IncludeProjectMetadataReferences => (_kind & AssetPathKind.ProjectMetadataReferences) != 0; public bool IncludeProjectAnalyzerReferences => (_kind & AssetPathKind.ProjectAnalyzerReferences) != 0; + public bool IncludeDocumentStateChecksums => (_kind & AssetPathKind.DocumentStateChecksums) != 0; + public bool IncludeDocumentAttributes => (_kind & AssetPathKind.DocumentAttributes) != 0; + public bool IncludeDocumentText => (_kind & AssetPathKind.DocumentText) != 0; + public static implicit operator AssetPath(AssetPathKind kind) => new(kind); /// @@ -110,8 +115,12 @@ internal enum AssetPathKind /// Projects = ProjectStateChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, + DocumentStateChecksums = 1 << 8, + DocumentAttributes = 1 << 9, + DocumentText = 1 << 10, + /// /// Search documents for results. /// - Documents = 1 << 8, + Documents = DocumentStateChecksums | DocumentAttributes | DocumentText, } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 80e04bb9ed8e8..e2a2d6fef2a46 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -55,19 +55,20 @@ public void AddAllTo(HashSet checksums) [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1333566", AllowGenericEnumeration = false)] internal static async Task FindAsync( + AssetPath assetPath, TextDocumentStates documentStates, - DocumentId? hintDocument, HashSet searchingChecksumsLeft, Dictionary result, 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, result, cancellationToken).ConfigureAwait(false); } } else @@ -80,7 +81,7 @@ 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, result, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2819a8d0e29c8..3282a601ba3e5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -136,7 +136,8 @@ public async Task FindAsync( Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); // This could either be the checksum for the text (which we'll use our regular helper for first)... - await ChecksumCollection.FindAsync(compilationState.FrozenSourceGeneratedDocumentStates, hintDocument: null, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync( + assetPath: AssetPathKind.Documents, compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, 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. @@ -417,7 +418,7 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { - if (assetPath.IncludeProjectChecksums && searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) @@ -441,11 +442,9 @@ public async Task FindAsync( if (assetPath.IncludeDocuments) { - var hintDocument = assetPath.DocumentId; - - 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); + await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); } } } @@ -486,6 +485,7 @@ public static DocumentStateChecksums Deserialize(ObjectReader reader) } public async Task FindAsync( + AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, Dictionary result, @@ -495,20 +495,14 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); - if (searchingChecksumsLeft.Remove(Checksum)) - { + if (assetPath.IncludeDocumentStateChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; - } - if (searchingChecksumsLeft.Remove(Info)) - { + if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) result[Info] = state.Attributes; - } - if (searchingChecksumsLeft.Remove(Text)) - { + if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) result[Text] = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); - } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index e005904e72e76..6306a5e55f1e6 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -258,7 +258,7 @@ async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksu // First, fetch all the DocumentStateChecksums for all the documents in the project. using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); await this.SynchronizeAssetsAsync>( - assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), checksums, + assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), allDocumentStateChecksums, cancellationToken).ConfigureAwait(false); @@ -274,7 +274,7 @@ await this.SynchronizeAssetsAsync( - assetPath: AssetPath.DocumentsInProject(projectChecksums.ProjectId), + assetPath: new(AssetPathKind.DocumentAttributes | AssetPathKind.DocumentText, projectChecksums.ProjectId), checksums).ConfigureAwait(false); } From b4efb7f6ba26590e25a1142b033eb5603942ef7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:36:25 -0700 Subject: [PATCH 0124/1047] Updates --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 6306a5e55f1e6..70950aeba88bf 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -112,7 +112,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() // and then the top level project-state-checksum values only. No other project data or document data will be // looked at. await this.SynchronizeAssetsAsync>( - assetPath: new(AssetPathKind.Solution | AssetPathKind.ProjectChecksums), + assetPath: new(AssetPathKind.Solution | AssetPathKind.ProjectStateChecksums), checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index d12f4b38a7eeb..68eed2d354e8a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -241,7 +241,7 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary Date: Thu, 11 Apr 2024 12:37:23 -0700 Subject: [PATCH 0125/1047] speeling --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index d109b99dad76e..8152cd0d6beb2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -274,7 +274,7 @@ await _assetProvider.GetAssetsAsync( } // bulk sync added project assets fully since we'll definitely need that data, and we can fetch more - // efficiently in bulkd and in parallel. + // efficiently in bulk and in parallel. await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) From 6142d022700e722abc6afc16e4608bb09d623a61 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 12:39:09 -0700 Subject: [PATCH 0126/1047] Simplify --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 70950aeba88bf..b992037bcc4c0 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -112,7 +112,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() // and then the top level project-state-checksum values only. No other project data or document data will be // looked at. await this.SynchronizeAssetsAsync>( - assetPath: new(AssetPathKind.Solution | AssetPathKind.ProjectStateChecksums), + assetPath: AssetPathKind.Solution | AssetPathKind.ProjectStateChecksums, checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); From e482b6e281d7db727fcb086ebbfae44dcba82c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 11 Apr 2024 13:51:03 -0700 Subject: [PATCH 0127/1047] Workaround for ILanguageService exports with no Layer (#72986) --- .../Core/Workspace/Mef/LanguageServiceMetadata.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 From 9cdb0bc3818704da7526e581ee377eb56a4c883b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:06:19 -0700 Subject: [PATCH 0128/1047] Do not capture --- .../Remote/ServiceHub/Host/AssetProvider.cs | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index b992037bcc4c0..3083d937ebe56 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -120,9 +120,10 @@ await this.SynchronizeAssetsAsync>( using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); // fourth, get all projects and documents in the solution - foreach (var (projectChecksum, _) in stateChecksums.Projects) + foreach (var (projectChecksum, projectId) in stateChecksums.Projects) { var projectStateChecksums = (ProjectStateChecksums)checksumToObjects[projectChecksum]; + Contract.ThrowIfTrue(projectStateChecksums.ProjectId != projectId); allProjectStateChecksums.Add(projectStateChecksums); } @@ -169,7 +170,8 @@ public async ValueTask SynchronizeProjectAssetsAsync( // 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)); + tasks.Add(SynchronizeProjectAssetsWorkerAsync( + tempBuffer, singleProjectChecksums.ProjectId, freeArrayBuilder: true, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -181,40 +183,42 @@ public async ValueTask SynchronizeProjectAssetsAsync( await SynchronizeProjectAssetsWorkerAsync( allProjectChecksums, projectId: null, - freeArrayBuilder: false).ConfigureAwait(false); + freeArrayBuilder: false, cancellationToken).ConfigureAwait(false); } } + } - async Task SynchronizeProjectAssetsWorkerAsync( - ArrayBuilder allProjectChecksums, ProjectId? projectId, bool freeArrayBuilder) + private async Task SynchronizeProjectAssetsWorkerAsync( + ArrayBuilder allProjectChecksums, ProjectId? projectId, bool freeArrayBuilder, CancellationToken cancellationToken) + { + try { - try - { - await Task.Yield(); + await Task.Yield(); - using var _ = ArrayBuilder.GetInstance(out var tasks); + 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)); + // 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)); + // Then sync each project's documents in parallel with each other. + foreach (var projectChecksums in allProjectChecksums) + tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums)); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - finally - { - if (freeArrayBuilder) - allProjectChecksums.Free(); - } + await Task.WhenAll(tasks).ConfigureAwait(false); } + finally + { + if (freeArrayBuilder) + allProjectChecksums.Free(); + } + + return; static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) { From c9193fda386a30c73b474ffc0fc36acc2167c24b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:12:38 -0700 Subject: [PATCH 0129/1047] Finer grained kinds --- .../Portable/Workspace/Solution/AssetPath.cs | 19 +++++++----- .../Remote/Core/AbstractAssetProvider.cs | 30 +++++++++---------- .../Remote/ServiceHub/Host/AssetProvider.cs | 7 +++-- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 6001848ab5e0d..3cf62a46c1b19 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -16,11 +16,6 @@ namespace Microsoft.CodeAnalysis.Serialization; [DataContract] internal readonly struct AssetPath { - /// - /// Instance that will only look up solution-level data when searching for checksums. - /// - public static readonly AssetPath SolutionOnly = AssetPathKind.Solution; - /// /// 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. @@ -49,6 +44,16 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do 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 IncludeSolution => (_kind & AssetPathKind.Solution) != 0; public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; @@ -70,12 +75,12 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do /// /// Searches only for information about this project. /// - public static implicit operator AssetPath(ProjectId projectId) => new(AssetPathKind.Projects, projectId, documentId: null); + 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.ProjectId, documentId); + public static implicit operator AssetPath(DocumentId documentId) => new(AssetPathKind.Documents, documentId); /// /// Searches the requested project, and all documents underneath it. Used only in tests. diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 73c56bf579f7c..9ad31f0be8ef4 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -27,17 +27,17 @@ internal abstract class AbstractAssetProvider public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - var solutionCompilationChecksums = await GetAssetAsync(AssetPath.SolutionOnly, solutionChecksum, cancellationToken).ConfigureAwait(false); - var solutionChecksums = await GetAssetAsync(AssetPath.SolutionOnly, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + var solutionCompilationChecksums = await GetAssetAsync(AssetPathKind.Solution, solutionChecksum, cancellationToken).ConfigureAwait(false); + var solutionChecksums = await GetAssetAsync(AssetPathKind.Solution, solutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - var solutionAttributes = await GetAssetAsync(AssetPath.SolutionOnly, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - await GetAssetAsync(AssetPath.SolutionOnly, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + var solutionAttributes = await GetAssetAsync(AssetPathKind.Solution, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(AssetPathKind.Solution, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, 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 GetAssetsAsync(AssetPath.SolutionOnly, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(AssetPathKind.Solution, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); return SolutionInfo.Create( solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects.ToImmutableAndClear(), analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); @@ -45,19 +45,19 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu public async Task CreateProjectInfoAsync(ProjectId projectId, Checksum projectChecksum, CancellationToken cancellationToken) { - var projectChecksums = await GetAssetAsync(assetPath: projectId, projectChecksum, cancellationToken).ConfigureAwait(false); + var projectChecksums = await GetAssetAsync(new(AssetPathKind.ProjectStateChecksums, projectId), projectChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(projectId == projectChecksums.ProjectId); - var attributes = await GetAssetAsync(assetPath: 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(assetPath: projectId, projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); - var parseOptions = await GetAssetAsync(assetPath: projectId, projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); + var parseOptions = await GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); - var projectReferences = await GetAssetsAsync(assetPath: projectId, projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await GetAssetsAsync(assetPath: projectId, projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await GetAssetsAsync(assetPath: projectId, projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var projectReferences = await GetAssetsAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); + var metadataReferences = await GetAssetsAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); var documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); @@ -92,11 +92,11 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId public async Task CreateDocumentInfoAsync( DocumentId documentId, Checksum documentChecksum, CancellationToken cancellationToken) { - var documentSnapshot = await GetAssetAsync(assetPath: documentId, documentChecksum, cancellationToken).ConfigureAwait(false); + var documentSnapshot = await GetAssetAsync(new(AssetPathKind.DocumentStateChecksums, documentId), documentChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); - var attributes = await GetAssetAsync(assetPath: documentId, documentSnapshot.Info, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await GetAssetAsync(assetPath: documentId, documentSnapshot.Text, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), documentSnapshot.Info, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), documentSnapshot.Text, cancellationToken).ConfigureAwait(false); var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); var textLoader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), attributes.FilePath)); diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 3083d937ebe56..8527fe60d516e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -89,17 +89,18 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() { // first, get top level solution state for the given solution checksum var compilationStateChecksums = await this.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, solutionChecksum, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.Solution, solutionChecksum, cancellationToken).ConfigureAwait(false); using var _1 = PooledHashSet.GetInstance(out var checksums); // second, get direct children of the solution compilation state. compilationStateChecksums.AddAllTo(checksums); - await this.SynchronizeAssetsAsync(assetPath: AssetPath.SolutionOnly, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await this.SynchronizeAssetsAsync( + assetPath: AssetPathKind.Solution, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); // third, get direct children of the solution state. var stateChecksums = await this.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.Solution, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); // Ask for solutions and top-level projects as the solution checksums will contain the checksums for // the project states and we want to get that all in one batch. From 0509407a5c2c7052a943d68b28f8d5e4e8edfe84 Mon Sep 17 00:00:00 2001 From: dotnet bot Date: Thu, 11 Apr 2024 14:18:12 -0700 Subject: [PATCH 0130/1047] Localized file check-in by OneLocBuild Task: Build definition ID 327: Build ID 2428680 --- .../Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf index a32bff0548d13..127d6cb443c23 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.cs.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Přepnout návrhy na přejmenování (Ctrl+Mezerník) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf index dea61db3524ae..80e15c424d8ba 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.de.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Umbenennungsvorschläge umschalten (STRG+LEERTASTE) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf index bcf089b6b8c2a..29fe71c06420a 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.es.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Alternar sugerencias de cambio de nombre (Ctrl+Espacio) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf index e678307ffa423..1d0d62f9f6aa3 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.fr.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Activer/désactiver les suggestions de changement de nom (Ctrl+Space) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf index 1a7ded6c1e19a..840ef6ec07113 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.it.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Attiva/Disattiva i suggerimenti di ridenominazione (CTRL+BARRA SPAZIATRICE) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf index 873425e9392c9..d279600b24680 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ja.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + 名前変更の提示の切り替え (Ctrl + Space キー) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf index e91e28175e39f..4a4691d5dbec8 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ko.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + 이름 바꾸기 제안 토글(Ctrl+스페이스바) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf index afdce182da2cb..636d9dd756e73 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pl.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Przełącz zmiany nazwy sugestii (Ctrl+Spacja) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf index bbb19cb2a3453..e76ab831cd398 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.pt-BR.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Alternar sugestões de renomeação (Ctrl+Espaço) From cb797bd7b06ec33f17bf0f5eb7ec8cc437bd80a9 Mon Sep 17 00:00:00 2001 From: dotnet bot Date: Thu, 11 Apr 2024 14:19:15 -0700 Subject: [PATCH 0131/1047] Localized file check-in by OneLocBuild Task: Build definition ID 327: Build ID 2428680 --- .../Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf | 2 +- .../Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf index 50eb6c2ef8c5f..0821209d935d0 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.ru.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Переключение подбора имени (CTRL+ПРОБЕЛ) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf index 7f1626305c8a1..58b1ccd2fb2be 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.tr.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + Yeniden adlandırma önerilerini değiştir (Ctrl+Space) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf index d0104de321777..bf661f21aa9a4 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hans.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + 切换重命名建议(Ctrl+空格) diff --git a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf index 5f7eecc96cca7..0ca22030737da 100644 --- a/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core.Wpf/xlf/EditorFeaturesWpfResources.zh-Hant.xlf @@ -74,7 +74,7 @@ Toggle rename suggestions (Ctrl+Space) - Toggle rename suggestions (Ctrl+Space) + 切換重新命名建議 (Ctrl+空格鍵) From 84e2b7a8c81906557d9d33c0e971bd3cdaf4357a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:23:15 -0700 Subject: [PATCH 0132/1047] Finer grained kinds --- .../Portable/Workspace/Solution/AssetPath.cs | 42 +++++++++++++------ .../Workspace/Solution/StateChecksums.cs | 16 ++++--- .../Remote/Core/AbstractAssetProvider.cs | 10 ++--- .../Remote/ServiceHub/Host/AssetProvider.cs | 4 +- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 3cf62a46c1b19..dd122adfb3f2b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -58,6 +58,13 @@ public AssetPath(AssetPathKind kind, DocumentId documentId) public bool IncludeProjects => (_kind & AssetPathKind.Projects) != 0; public bool IncludeDocuments => (_kind & AssetPathKind.Documents) != 0; + public bool IncludeSolutionCompilationStateChecksums => (_kind & AssetPathKind.SolutionCompilationStateChecksums) != 0; + public bool IncludeSolutionStateChecksums => (_kind & AssetPathKind.SolutionStateChecksums) != 0; + public bool IncludeSolutionSourceGeneratorExecutionVersionMap => (_kind & AssetPathKind.SolutionSourceGeneratorExecutionVersionMap) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentStates => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentStates) != 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; @@ -102,28 +109,37 @@ public static AssetPath DocumentsInProject(ProjectId projectId) [Flags] internal enum AssetPathKind { + SolutionCompilationStateChecksums = 1 << 0, + SolutionStateChecksums = 1 << 1, + SolutionSourceGeneratorExecutionVersionMap = 1 << 2, + SolutionFrozenSourceGeneratedDocumentStates = 1 << 3, + SolutionAttributes = 1 << 4, + SolutionAnalyzerReferences = 1 << 5, + + // Keep a gap so we can easily add more solution kinds + ProjectStateChecksums = 1 << 10, + ProjectAttributes = 1 << 11, + ProjectCompilationOptions = 1 << 12, + ProjectParseOptions = 1 << 13, + ProjectProjectReferences = 1 << 14, + ProjectMetadataReferences = 1 << 15, + ProjectAnalyzerReferences = 1 << 16, + + // Keep a gap so we can easily add more project kinds + DocumentStateChecksums = 1 << 20, + DocumentAttributes = 1 << 21, + DocumentText = 1 << 22, + /// /// Search solution-level information. /// - Solution = 1 << 0, - - ProjectStateChecksums = 1 << 1, - ProjectAttributes = 1 << 2, - ProjectCompilationOptions = 1 << 3, - ProjectParseOptions = 1 << 4, - ProjectProjectReferences = 1 << 5, - ProjectMetadataReferences = 1 << 6, - ProjectAnalyzerReferences = 1 << 7, + Solution = SolutionCompilationStateChecksums | SolutionStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentStates | SolutionAttributes | SolutionAnalyzerReferences, /// /// Search projects for results. All project-level information will be searched. /// Projects = ProjectStateChecksums | ProjectAttributes | ProjectCompilationOptions | ProjectParseOptions | ProjectProjectReferences | ProjectMetadataReferences | ProjectAnalyzerReferences, - DocumentStateChecksums = 1 << 8, - DocumentAttributes = 1 << 9, - DocumentText = 1 << 10, - /// /// Search documents for results. /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 3282a601ba3e5..23f42a0b465c8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -122,16 +122,13 @@ public async Task FindAsync( if (assetPath.IncludeSolution) { - if (searchingChecksumsLeft.Remove(this.Checksum)) + if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) result[this.Checksum] = this; - if (searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) + if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; - if (searchingChecksumsLeft.Count == 0) - return; - - if (compilationState.FrozenSourceGeneratedDocumentStates != null) + if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentStates && compilationState.FrozenSourceGeneratedDocumentStates != null) { Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); @@ -249,13 +246,14 @@ public async Task FindAsync( if (assetPath.IncludeSolution) { - if (searchingChecksumsLeft.Remove(Checksum)) + if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; - if (searchingChecksumsLeft.Remove(Attributes)) + if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) result[Attributes] = solution.SolutionAttributes; - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + if (assetPath.IncludeSolutionAnalyzerReferences) + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); } if (searchingChecksumsLeft.Count == 0) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 9ad31f0be8ef4..1365609946c5a 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -27,17 +27,17 @@ internal abstract class AbstractAssetProvider public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { - var solutionCompilationChecksums = await GetAssetAsync(AssetPathKind.Solution, solutionChecksum, cancellationToken).ConfigureAwait(false); - var solutionChecksums = await GetAssetAsync(AssetPathKind.Solution, solutionCompilationChecksums.SolutionState, 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.Solution, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); - await GetAssetAsync(AssetPathKind.Solution, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); + await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, 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 GetAssetsAsync(AssetPathKind.Solution, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await GetAssetsAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); return SolutionInfo.Create( solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects.ToImmutableAndClear(), analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 8527fe60d516e..8d6953380f39f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -89,7 +89,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() { // first, get top level solution state for the given solution checksum var compilationStateChecksums = await this.GetAssetAsync( - assetPath: AssetPathKind.Solution, solutionChecksum, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); using var _1 = PooledHashSet.GetInstance(out var checksums); @@ -100,7 +100,7 @@ await this.SynchronizeAssetsAsync( // third, get direct children of the solution state. var stateChecksums = await this.GetAssetAsync( - assetPath: AssetPathKind.Solution, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.SolutionStateChecksums, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); // Ask for solutions and top-level projects as the solution checksums will contain the checksums for // the project states and we want to get that all in one batch. From 6d3c081ae709cc4a626059db22e36536ad0e0e06 Mon Sep 17 00:00:00 2001 From: dotnet bot Date: Thu, 11 Apr 2024 14:23:29 -0700 Subject: [PATCH 0133/1047] Localized file check-in by OneLocBuild Task: Build definition ID 327: Build ID 2428680 --- src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index c623581add8b4..ab360b14e83e8 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -2271,7 +2271,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 From 633f604cd12a568eab2590af827ba43454e30e12 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:31:56 -0700 Subject: [PATCH 0134/1047] in progress --- .../Workspace/Solution/StateChecksums.cs | 3 ++- .../Host/RemoteWorkspace.SolutionCreator.cs | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 23f42a0b465c8..4c85ad0bcdcec 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -134,7 +134,8 @@ public async Task FindAsync( // This could either be the checksum for the text (which we'll use our regular helper for first)... await ChecksumCollection.FindAsync( - assetPath: AssetPathKind.Documents, compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + new AssetPath(AssetPathKind.Documents, assetPath.ProjectId, assetPath.DocumentId), + compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, 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. diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 8152cd0d6beb2..1baf8a350ab1b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -37,12 +37,12 @@ private readonly struct SolutionCreator(HostServices hostServices, AssetProvider public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, newSolutionCompilationChecksums.SolutionState, cancellationToken).ConfigureAwait(false); var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, 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; @@ -59,9 +59,9 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca solution = solution.WithoutFrozenSourceGeneratedDocuments(); var newSolutionCompilationChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksum, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, newSolutionChecksum, cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, 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); @@ -69,7 +69,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.Attributes != newSolutionChecksums.Attributes) { var newSolutionInfo = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, 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); @@ -84,7 +84,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { solution = solution.WithAnalyzerReferences(await _assetProvider.GetAssetsAsync( - assetPath: AssetPath.SolutionOnly, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); + AssetPathKind.SolutionAnalyzerReferences, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && @@ -96,13 +96,15 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca for (var i = 0; i < count; i++) { + var assetPath = new AssetPath(AssetPathKind.SolutionFrozenSourceGeneratedDocumentStates, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i]); var identity = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); + assetPath, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); var documentStateChecksums = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); + assetPath, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); - var serializableSourceText = await _assetProvider.GetAssetAsync(assetPath: newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i], documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync( + assetPath, documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); var generationDateTime = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes[i]; var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -116,7 +118,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap) { var newVersions = await _assetProvider.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, newSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + 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 From b04403966178bb38a8c2a7add007ec66a0536446 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:33:03 -0700 Subject: [PATCH 0135/1047] in progress --- .../Core/Portable/Workspace/Solution/AssetPath.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index dd122adfb3f2b..30ef702e2906a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -44,13 +44,13 @@ public AssetPath(AssetPathKind kind, ProjectId? projectId = null, DocumentId? do DocumentId = documentId; } - public AssetPath(AssetPathKind kind, ProjectId projectId) + public AssetPath(AssetPathKind kind, ProjectId? projectId) : this(kind, projectId, documentId: null) { } - public AssetPath(AssetPathKind kind, DocumentId documentId) - : this(kind, documentId.ProjectId, documentId) + public AssetPath(AssetPathKind kind, DocumentId? documentId) + : this(kind, documentId?.ProjectId, documentId) { } From 5569fb55d1550f71dc11e2f6909e4da39e1a47eb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:38:47 -0700 Subject: [PATCH 0136/1047] in progress --- .../Portable/Workspace/Solution/AssetPath.cs | 55 ++++++++++++------- .../Workspace/Solution/StateChecksums.cs | 6 +- .../Host/RemoteWorkspace.SolutionCreator.cs | 4 +- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 30ef702e2906a..1e81575c82ca9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -20,7 +20,7 @@ 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.Solution | AssetPathKind.Projects | AssetPathKind.Documents; + public static readonly AssetPath FullLookupForTesting = AssetPathKind.SolutionCompilationState | AssetPathKind.SolutionState | AssetPathKind.Projects | AssetPathKind.Documents; [DataMember(Order = 0)] private readonly AssetPathKind _kind; @@ -54,14 +54,18 @@ public AssetPath(AssetPathKind kind, DocumentId? documentId) { } - public bool IncludeSolution => (_kind & AssetPathKind.Solution) != 0; + 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 IncludeSolutionStateChecksums => (_kind & AssetPathKind.SolutionStateChecksums) != 0; public bool IncludeSolutionSourceGeneratorExecutionVersionMap => (_kind & AssetPathKind.SolutionSourceGeneratorExecutionVersionMap) != 0; - public bool IncludeSolutionFrozenSourceGeneratedDocumentStates => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentStates) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentIdentities => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities) != 0; + public bool IncludeSolutionFrozenSourceGeneratedDocumentStateChecksums => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentStateChecksums) != 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; @@ -95,7 +99,7 @@ public AssetPath(AssetPathKind kind, DocumentId? documentId) /// /// public static AssetPath SolutionAndProjectForTesting(ProjectId projectId) - => new(AssetPathKind.Solution | AssetPathKind.Projects, projectId); + => new(AssetPathKind.SolutionCompilationState | AssetPathKind.SolutionState | AssetPathKind.Projects, projectId); /// /// Searches all documents within the specified project. @@ -110,30 +114,39 @@ public static AssetPath DocumentsInProject(ProjectId projectId) internal enum AssetPathKind { SolutionCompilationStateChecksums = 1 << 0, - SolutionStateChecksums = 1 << 1, SolutionSourceGeneratorExecutionVersionMap = 1 << 2, - SolutionFrozenSourceGeneratedDocumentStates = 1 << 3, - SolutionAttributes = 1 << 4, - SolutionAnalyzerReferences = 1 << 5, + SolutionFrozenSourceGeneratedDocumentIdentities = 1 << 3, + SolutionFrozenSourceGeneratedDocumentStateChecksums = 1 << 4, + SolutionFrozenSourceGeneratedDocumentText = 1 << 5, + + // 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 << 10, - ProjectAttributes = 1 << 11, - ProjectCompilationOptions = 1 << 12, - ProjectParseOptions = 1 << 13, - ProjectProjectReferences = 1 << 14, - ProjectMetadataReferences = 1 << 15, - ProjectAnalyzerReferences = 1 << 16, + 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 - DocumentStateChecksums = 1 << 20, - DocumentAttributes = 1 << 21, - DocumentText = 1 << 22, + DocumentStateChecksums = 1 << 25, + DocumentAttributes = 1 << 26, + DocumentText = 1 << 27, + + /// + /// Search solution-compilation-state level information. + /// + SolutionCompilationState = SolutionCompilationStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentIdentities | SolutionFrozenSourceGeneratedDocumentStateChecksums | SolutionFrozenSourceGeneratedDocumentText, /// - /// Search solution-level information. + /// Search solution-state level information. /// - Solution = SolutionCompilationStateChecksums | SolutionStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentStates | SolutionAttributes | SolutionAnalyzerReferences, + SolutionState = SolutionStateChecksums | SolutionAttributes | SolutionAnalyzerReferences, /// /// Search projects for results. All project-level information will be searched. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 4c85ad0bcdcec..ea2202b4c9515 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -120,7 +120,7 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (assetPath.IncludeSolution) + if (assetPath.IncludeSolutionCompilationState) { if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) result[this.Checksum] = this; @@ -128,7 +128,7 @@ public async Task FindAsync( if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; - if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentStates && compilationState.FrozenSourceGeneratedDocumentStates != null) + if (compilationState.FrozenSourceGeneratedDocumentStates != null) { Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); @@ -245,7 +245,7 @@ public async Task FindAsync( if (searchingChecksumsLeft.Count == 0) return; - if (assetPath.IncludeSolution) + if (assetPath.IncludeSolutionState) { if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) result[Checksum] = this; diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 1baf8a350ab1b..4447093b6b07b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -92,7 +92,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca !newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes.IsDefault) { var count = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value.Count; - var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); + using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); for (var i = 0; i < count; i++) { @@ -111,7 +111,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca frozenDocuments.Add((identity, generationDateTime, text)); } - solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.ToImmutable()); + solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.ToImmutableAndClear()); } if (oldSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap != From 2ff97df16b670a42d52ec95afcc54c551acc22c0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:40:08 -0700 Subject: [PATCH 0137/1047] in progress --- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 4447093b6b07b..2d61d2969852e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -96,15 +96,15 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca for (var i = 0; i < count; i++) { - var assetPath = new AssetPath(AssetPathKind.SolutionFrozenSourceGeneratedDocumentStates, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i]); + var frozenDocumentId = newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i]; var identity = await _assetProvider.GetAssetAsync( - assetPath, newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities, frozenDocumentId), newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); var documentStateChecksums = await _assetProvider.GetAssetAsync( - assetPath, newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentStateChecksums, frozenDocumentId), newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Checksums[i], cancellationToken).ConfigureAwait(false); var serializableSourceText = await _assetProvider.GetAssetAsync( - assetPath, documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentText, frozenDocumentId), documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); var generationDateTime = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes[i]; var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); From 10f5b5e9f3fa105e12391b2e6e26de46ab75e24f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:43:48 -0700 Subject: [PATCH 0138/1047] in progress --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index ea2202b4c9515..16de0c64ae96b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -133,9 +133,12 @@ public async Task FindAsync( Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); // This could either be the checksum for the text (which we'll use our regular helper for first)... - await ChecksumCollection.FindAsync( - new AssetPath(AssetPathKind.Documents, assetPath.ProjectId, assetPath.DocumentId), - compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentText) + { + await ChecksumCollection.FindAsync( + new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), + compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, 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. From 9c9f17b87ecc843cf38eeb09ecca2e8e15287417 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:49:00 -0700 Subject: [PATCH 0139/1047] Only doc text --- .../Workspace/Solution/StateChecksums.cs | 20 +++++++++++-------- .../Workspace/Solution/TextDocumentStates.cs | 11 ++++++++-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 16de0c64ae96b..44ca7768ca2c6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -22,18 +22,18 @@ public SolutionCompilationStateChecksums( Checksum solutionState, Checksum sourceGeneratorExecutionVersionMap, ChecksumCollection? frozenSourceGeneratedDocumentIdentities, - ChecksumsAndIds? frozenSourceGeneratedDocuments, + ChecksumsAndIds? frozenSourceGeneratedDocumentTexts, 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. - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocuments.HasValue); - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocuments?.Length); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocumentTexts.HasValue); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocumentTexts?.Length); SolutionState = solutionState; SourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; - FrozenSourceGeneratedDocuments = frozenSourceGeneratedDocuments; + FrozenSourceGeneratedDocumentTexts = frozenSourceGeneratedDocumentTexts; FrozenSourceGeneratedDocumentGenerationDateTimes = frozenSourceGeneratedDocumentGenerationDateTimes; // note: intentionally not mixing in FrozenSourceGeneratedDocumentGenerationDateTimes as that is not part of the @@ -42,14 +42,18 @@ public SolutionCompilationStateChecksums( SolutionState, SourceGeneratorExecutionVersionMap, FrozenSourceGeneratedDocumentIdentities?.Checksum ?? Checksum.Null, - FrozenSourceGeneratedDocuments?.Checksum ?? Checksum.Null); + frozenSourceGeneratedDocumentTexts?.Checksum ?? Checksum.Null); } public Checksum Checksum { get; } public Checksum SolutionState { get; } public Checksum SourceGeneratorExecutionVersionMap { get; } public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } - public ChecksumsAndIds? FrozenSourceGeneratedDocuments { get; } + + /// + /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. + /// + public ChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } // note: intentionally not part of the identity contract of this type. public ImmutableArray FrozenSourceGeneratedDocumentGenerationDateTimes { get; } @@ -60,7 +64,7 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.SolutionState); checksums.AddIfNotNullChecksum(this.SourceGeneratorExecutionVersionMap); this.FrozenSourceGeneratedDocumentIdentities?.AddAllTo(checksums); - this.FrozenSourceGeneratedDocuments?.Checksums.AddAllTo(checksums); + this.FrozenSourceGeneratedDocumentTexts?.Checksums.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -75,7 +79,7 @@ public void Serialize(ObjectWriter writer) if (FrozenSourceGeneratedDocumentIdentities.HasValue) { this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); - this.FrozenSourceGeneratedDocuments!.Value.WriteTo(writer); + this.FrozenSourceGeneratedDocumentTexts!.Value.WriteTo(writer); writer.WriteArray(this.FrozenSourceGeneratedDocumentGenerationDateTimes, static (w, d) => w.WriteInt64(d.Ticks)); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index d1740d26d7500..761f2e0a5a9ad 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -295,8 +295,15 @@ public int Compare(DocumentId? x, DocumentId? y) public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) { - var documentChecksumTasks = SelectAsArray(static (state, token) => state.GetChecksumAsync(token), cancellationToken); - var documentChecksums = new ChecksumCollection(await documentChecksumTasks.WhenAll().ConfigureAwait(false)); + var documentTextChecksums = await SelectAsArrayAsync(static async (state, _, cancellationToken) => + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + return stateChecksums.Text; + }, + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); + + var documentChecksums = new ChecksumCollection(documentTextChecksums); return new(documentChecksums, SelectAsArray(static s => s.Id)); } From 1229821fb33c9a1f6f69ca58433e001657f61220 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:55:55 -0700 Subject: [PATCH 0140/1047] in progress --- .../Portable/Workspace/Solution/AssetPath.cs | 6 ++---- .../Workspace/Solution/StateChecksums.cs | 12 +++++++++++ .../Remote/ServiceHub/Host/AssetProvider.cs | 4 ++-- .../Host/RemoteWorkspace.SolutionCreator.cs | 20 ++++++++++--------- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 1e81575c82ca9..38de8a081c3ef 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -62,7 +62,6 @@ public AssetPath(AssetPathKind kind, DocumentId? documentId) public bool IncludeSolutionCompilationStateChecksums => (_kind & AssetPathKind.SolutionCompilationStateChecksums) != 0; public bool IncludeSolutionSourceGeneratorExecutionVersionMap => (_kind & AssetPathKind.SolutionSourceGeneratorExecutionVersionMap) != 0; public bool IncludeSolutionFrozenSourceGeneratedDocumentIdentities => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities) != 0; - public bool IncludeSolutionFrozenSourceGeneratedDocumentStateChecksums => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentStateChecksums) != 0; public bool IncludeSolutionFrozenSourceGeneratedDocumentText => (_kind & AssetPathKind.SolutionFrozenSourceGeneratedDocumentText) != 0; public bool IncludeSolutionStateChecksums => (_kind & AssetPathKind.SolutionStateChecksums) != 0; @@ -116,8 +115,7 @@ internal enum AssetPathKind SolutionCompilationStateChecksums = 1 << 0, SolutionSourceGeneratorExecutionVersionMap = 1 << 2, SolutionFrozenSourceGeneratedDocumentIdentities = 1 << 3, - SolutionFrozenSourceGeneratedDocumentStateChecksums = 1 << 4, - SolutionFrozenSourceGeneratedDocumentText = 1 << 5, + SolutionFrozenSourceGeneratedDocumentText = 1 << 4, // Keep a gap so we can easily add more solution compilation state kinds SolutionStateChecksums = 1 << 10, @@ -141,7 +139,7 @@ internal enum AssetPathKind /// /// Search solution-compilation-state level information. /// - SolutionCompilationState = SolutionCompilationStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentIdentities | SolutionFrozenSourceGeneratedDocumentStateChecksums | SolutionFrozenSourceGeneratedDocumentText, + SolutionCompilationState = SolutionCompilationStateChecksums | SolutionSourceGeneratorExecutionVersionMap | SolutionFrozenSourceGeneratedDocumentIdentities | SolutionFrozenSourceGeneratedDocumentText, /// /// Search solution-state level information. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 44ca7768ca2c6..d3810c1633c24 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -146,6 +146,18 @@ await ChecksumCollection.FindAsync( // ... 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 documentId = assetPath.DocumentId; + foreach(var identity in FrozenSourceGeneratedDocumentIdentities.Value) + { + if (documentId != null) + { + if (identity.do) + } + } + } + for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) { var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 8d6953380f39f..2682fe95f3d3f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -96,7 +96,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() // second, get direct children of the solution compilation state. compilationStateChecksums.AddAllTo(checksums); await this.SynchronizeAssetsAsync( - assetPath: AssetPathKind.Solution, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.SolutionCompilationState, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); // third, get direct children of the solution state. var stateChecksums = await this.GetAssetAsync( @@ -113,7 +113,7 @@ await this.SynchronizeAssetsAsync( // and then the top level project-state-checksum values only. No other project data or document data will be // looked at. await this.SynchronizeAssetsAsync>( - assetPath: AssetPathKind.Solution | AssetPathKind.ProjectStateChecksums, + assetPath: AssetPathKind.SolutionState | AssetPathKind.ProjectStateChecksums, checksums, static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), arg: checksumToObjects, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 2d61d2969852e..b78af045a4138 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -88,23 +88,25 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && - newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.HasValue && + newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.HasValue && !newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes.IsDefault) { - var count = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value.Count; - using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); + var newSolutionFrozenSourceGeneratedDocumentIdentities = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value; + var newSolutionFrozenSourceGeneratedDocumentTexts = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.Value; + var count = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums.Count; + using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); for (var i = 0; i < count; i++) { - var frozenDocumentId = newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value.Ids[i]; - var identity = await _assetProvider.GetAssetAsync( - new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentIdentities, frozenDocumentId), newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value[i], cancellationToken).ConfigureAwait(false); + var frozenDocumentId = newSolutionFrozenSourceGeneratedDocumentTexts.Ids[i]; + var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums[i]; + var frozenDocumentIdentity = newSolutionFrozenSourceGeneratedDocumentIdentities[i]; - var documentStateChecksums = await _assetProvider.GetAssetAsync( - new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentStateChecksums, frozenDocumentId), 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( - new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentText, frozenDocumentId), documentStateChecksums.Text, cancellationToken).ConfigureAwait(false); + new(AssetPathKind.SolutionFrozenSourceGeneratedDocumentText, frozenDocumentId), frozenDocumentTextChecksum, cancellationToken).ConfigureAwait(false); var generationDateTime = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes[i]; var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); From 06dd3e53f2549a5f8fb0200f6ab8b09ad5d3dd93 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 14:58:58 -0700 Subject: [PATCH 0141/1047] in progress --- .../Solution/SolutionCompilationState_Checksum.cs | 11 ++++++----- .../Portable/Workspace/Solution/StateChecksums.cs | 15 ++++++++------- .../Workspace/Solution/TextDocumentStates.cs | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index 8b0f788488c64..af37ec21138f8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -125,16 +125,17 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo } ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocuments = null; + ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (FrozenSourceGeneratedDocumentStates != null) { var serializer = this.SolutionState.Services.GetRequiredService(); - var identityChecksums = FrozenSourceGeneratedDocumentStates - .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.GetTextChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentIdentities = new ChecksumCollection(identityChecksums); - frozenSourceGeneratedDocuments = await FrozenSourceGeneratedDocumentStates.GetChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentGenerationDateTimes = FrozenSourceGeneratedDocumentStates.SelectAsArray(d => d.GenerationDateTime); } @@ -146,8 +147,8 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo var compilationStateChecksums = new SolutionCompilationStateChecksums( solutionStateChecksum, versionMapChecksum, + frozenSourceGeneratedDocumentTexts, frozenSourceGeneratedDocumentIdentities, - frozenSourceGeneratedDocuments, frozenSourceGeneratedDocumentGenerationDateTimes); return (compilationStateChecksums, projectCone); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index d3810c1633c24..446bad9d3f1c7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -21,8 +21,9 @@ internal sealed class SolutionCompilationStateChecksums public SolutionCompilationStateChecksums( Checksum solutionState, Checksum sourceGeneratorExecutionVersionMap, - ChecksumCollection? frozenSourceGeneratedDocumentIdentities, + // These arrays are all the same length if present, and reference the same documents in the same order. ChecksumsAndIds? frozenSourceGeneratedDocumentTexts, + ChecksumCollection? frozenSourceGeneratedDocumentIdentities, ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes) { // For the frozen source generated document info, we expect two either have both checksum collections or neither, and they @@ -32,8 +33,8 @@ public SolutionCompilationStateChecksums( SolutionState = solutionState; SourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; - FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; FrozenSourceGeneratedDocumentTexts = frozenSourceGeneratedDocumentTexts; + FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; FrozenSourceGeneratedDocumentGenerationDateTimes = frozenSourceGeneratedDocumentGenerationDateTimes; // note: intentionally not mixing in FrozenSourceGeneratedDocumentGenerationDateTimes as that is not part of the @@ -48,12 +49,12 @@ public SolutionCompilationStateChecksums( public Checksum Checksum { get; } public Checksum SolutionState { get; } public Checksum SourceGeneratorExecutionVersionMap { get; } - public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } /// /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. /// public ChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } + public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } // note: intentionally not part of the identity contract of this type. public ImmutableArray FrozenSourceGeneratedDocumentGenerationDateTimes { get; } @@ -78,8 +79,8 @@ public void Serialize(ObjectWriter writer) writer.WriteBoolean(this.FrozenSourceGeneratedDocumentIdentities.HasValue); if (FrozenSourceGeneratedDocumentIdentities.HasValue) { - this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); this.FrozenSourceGeneratedDocumentTexts!.Value.WriteTo(writer); + this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); writer.WriteArray(this.FrozenSourceGeneratedDocumentGenerationDateTimes, static (w, d) => w.WriteInt64(d.Ticks)); } } @@ -91,22 +92,22 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) var sourceGeneratorExecutionVersionMap = Checksum.ReadFrom(reader); var hasFrozenSourceGeneratedDocuments = reader.ReadBoolean(); + ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocuments = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (hasFrozenSourceGeneratedDocuments) { + frozenSourceGeneratedDocumentTexts = ChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentIdentities = ChecksumCollection.ReadFrom(reader); - frozenSourceGeneratedDocuments = ChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentGenerationDateTimes = reader.ReadArray(r => new DateTime(r.ReadInt64())); } var result = new SolutionCompilationStateChecksums( solutionState: solutionState, sourceGeneratorExecutionVersionMap: sourceGeneratorExecutionVersionMap, + frozenSourceGeneratedDocumentTexts, frozenSourceGeneratedDocumentIdentities, - frozenSourceGeneratedDocuments, frozenSourceGeneratedDocumentGenerationDateTimes); Contract.ThrowIfFalse(result.Checksum == checksum); return result; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 761f2e0a5a9ad..04a56efde5bb0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -293,7 +293,7 @@ public int Compare(DocumentId? x, DocumentId? y) } } - public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) + public async ValueTask> GetTextChecksumsAndIdsAsync(CancellationToken cancellationToken) { var documentTextChecksums = await SelectAsArrayAsync(static async (state, _, cancellationToken) => { From 4639794964f2778a07ec3786526ebf35b61615e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 15:07:38 -0700 Subject: [PATCH 0142/1047] Flesh out --- .../Workspace/Solution/StateChecksums.cs | 34 ++++++++++++------- .../Workspace/Solution/TextDocumentStates.cs | 26 ++++++++++---- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 446bad9d3f1c7..28a9747b0a418 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Resources; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -136,6 +137,7 @@ public async Task FindAsync( if (compilationState.FrozenSourceGeneratedDocumentStates != null) { Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); + Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentTexts.HasValue); // This could either be the checksum for the text (which we'll use our regular helper for first)... if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentText) @@ -150,23 +152,31 @@ await ChecksumCollection.FindAsync( if (assetPath.IncludeSolutionFrozenSourceGeneratedDocumentIdentities) { var documentId = assetPath.DocumentId; - foreach(var identity in FrozenSourceGeneratedDocumentIdentities.Value) + if (documentId != null) { - if (documentId != null) + var index = FrozenSourceGeneratedDocumentTexts.Value.Ids.IndexOf(documentId); + if (index >= 0) { - if (identity.do) + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value.Children[index]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); + result[identityChecksum] = state.Identity; + } } } - } - - for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) - { - var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; - if (searchingChecksumsLeft.Remove(identityChecksum)) + else { - var id = FrozenSourceGeneratedDocuments!.Value.Ids[i]; - Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - result[identityChecksum] = state.Identity; + for (var i = 0; i < FrozenSourceGeneratedDocumentIdentities.Value.Count; i++) + { + var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; + if (searchingChecksumsLeft.Remove(identityChecksum)) + { + var id = FrozenSourceGeneratedDocumentTexts.Value.Ids[i]; + Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); + result[identityChecksum] = state.Identity; + } + } } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 04a56efde5bb0..0a0b27d970ab8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -293,15 +293,27 @@ public int Compare(DocumentId? x, DocumentId? y) } } + public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) + { + var documentTextChecksums = await SelectAsArrayAsync( + static async (state, _, cancellationToken) => await state.GetChecksumAsync(cancellationToken).ConfigureAwait(false), + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); + + var documentChecksums = new ChecksumCollection(documentTextChecksums); + return new(documentChecksums, SelectAsArray(static s => s.Id)); + } + public async ValueTask> GetTextChecksumsAndIdsAsync(CancellationToken cancellationToken) { - var documentTextChecksums = await SelectAsArrayAsync(static async (state, _, cancellationToken) => - { - var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - return stateChecksums.Text; - }, - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); + var documentTextChecksums = await SelectAsArrayAsync( + static async (state, _, cancellationToken) => + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + return stateChecksums.Text; + }, + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); var documentChecksums = new ChecksumCollection(documentTextChecksums); return new(documentChecksums, SelectAsArray(static s => s.Id)); From b7638f6522911981f8dd6ba5423c93d404ac9b0a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 15:18:32 -0700 Subject: [PATCH 0143/1047] tests --- .../Core/Test.Next/Remote/SerializationValidator.cs | 6 +++--- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index ac99bcc2d9fc3..a648ff1d14494 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -258,9 +258,9 @@ internal void SolutionCompilationStateEqual(SolutionCompilationStateChecksums so if (solutionObject1.FrozenSourceGeneratedDocumentIdentities.HasValue) AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentIdentities.Value, solutionObject2.FrozenSourceGeneratedDocumentIdentities!.Value); - Assert.Equal(solutionObject1.FrozenSourceGeneratedDocuments.HasValue, solutionObject2.FrozenSourceGeneratedDocuments.HasValue); - if (solutionObject1.FrozenSourceGeneratedDocuments.HasValue) - AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocuments.Value, solutionObject2.FrozenSourceGeneratedDocuments!.Value); + Assert.Equal(solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue, solutionObject2.FrozenSourceGeneratedDocumentTexts.HasValue); + if (solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue) + AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentTexts.Value, solutionObject2.FrozenSourceGeneratedDocumentTexts!.Value); } internal void SolutionStateEqual(SolutionStateChecksums solutionObject1, SolutionStateChecksums solutionObject2) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 68eed2d354e8a..c1f05df5cf279 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -115,9 +115,9 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec var set = new HashSet(); var solutionCompilationChecksums = await assetService.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, solutionChecksum, CancellationToken.None).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, CancellationToken.None).ConfigureAwait(false); var solutionChecksums = await assetService.GetAssetAsync( - assetPath: AssetPath.SolutionOnly, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, solutionCompilationChecksums.SolutionState, CancellationToken.None).ConfigureAwait(false); solutionCompilationChecksums.AddAllTo(set); solutionChecksums.AddAllTo(set); From ec5ad04432acf862b1d3f400a3afa9e0ba529c87 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 15:20:06 -0700 Subject: [PATCH 0144/1047] docs --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 28a9747b0a418..0c4478084e0a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -154,6 +154,7 @@ await ChecksumCollection.FindAsync( 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 = FrozenSourceGeneratedDocumentTexts.Value.Ids.IndexOf(documentId); if (index >= 0) { @@ -167,6 +168,7 @@ await ChecksumCollection.FindAsync( } 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]; From 26bba458a798a986532af3c6f46036a05b8f161a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:03:11 -0700 Subject: [PATCH 0145/1047] Stronger typed called --- .../Remote/ServiceHub/Host/AssetProvider.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 2682fe95f3d3f..bc9b804daae75 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -91,42 +91,36 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() var compilationStateChecksums = await this.GetAssetAsync( assetPath: AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); - using var _1 = PooledHashSet.GetInstance(out var checksums); - // second, get direct children of the solution compilation state. - compilationStateChecksums.AddAllTo(checksums); - await this.SynchronizeAssetsAsync( - assetPath: AssetPathKind.SolutionCompilationState, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - - // third, get direct children of the solution state. - var stateChecksums = await this.GetAssetAsync( + var solutionStateChecksum = await this.GetAssetAsync( assetPath: AssetPathKind.SolutionStateChecksums, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); // Ask for solutions and top-level projects as the solution checksums will contain the checksums for // the project states and we want to get that all in one batch. - checksums.Clear(); - stateChecksums.AddAllTo(checksums); + using var _1 = PooledHashSet.GetInstance(out var checksums); + solutionStateChecksum.AnalyzerReferences.AddAllTo(checksums); - using var _2 = PooledDictionary.GetInstance(out var checksumToObjects); + await this.SynchronizeAssetsAsync( + assetPath: AssetPathKind.SolutionAnalyzerReferences, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); // Note: this search will be optimized on the host side. It will search through the solution level values, // and then the top level project-state-checksum values only. No other project data or document data will be // looked at. - await this.SynchronizeAssetsAsync>( - assetPath: AssetPathKind.SolutionState | AssetPathKind.ProjectStateChecksums, + checksums.Clear(); + solutionStateChecksum.Projects.Checksums.AddAllTo(checksums); + + using var _2 = PooledDictionary.GetInstance(out var checksumToProjectStateChecksums); + await this.SynchronizeAssetsAsync>( + assetPath: AssetPathKind.ProjectStateChecksums, checksums, - static (checksum, asset, checksumToObjects) => checksumToObjects.Add(checksum, asset), - arg: checksumToObjects, cancellationToken).ConfigureAwait(false); + static (checksum, asset, checksumToProjectStateChecksums) => checksumToProjectStateChecksums.Add(checksum, asset), + arg: checksumToProjectStateChecksums, cancellationToken).ConfigureAwait(false); using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); // fourth, get all projects and documents in the solution - foreach (var (projectChecksum, projectId) in stateChecksums.Projects) - { - var projectStateChecksums = (ProjectStateChecksums)checksumToObjects[projectChecksum]; - Contract.ThrowIfTrue(projectStateChecksums.ProjectId != projectId); + foreach (var (_, projectStateChecksums) in checksumToProjectStateChecksums) allProjectStateChecksums.Add(projectStateChecksums); - } await SynchronizeProjectAssetsAsync(allProjectStateChecksums, cancellationToken).ConfigureAwait(false); } From 71c46b71450067277bea5cde5a111e4be94c87e4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:05:23 -0700 Subject: [PATCH 0146/1047] in progrss --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index bc9b804daae75..2dc0cf701aafd 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -277,8 +277,8 @@ await SynchronizeAssetsAsync( checksums).ConfigureAwait(false); } - ValueTask SynchronizeAssetsAsync(AssetPath assetPath, HashSet checksums) - => this.SynchronizeAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken); + async Task SynchronizeAssetsAsync(AssetPath assetPath, HashSet checksums) + => await this.SynchronizeAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } public async ValueTask SynchronizeAssetsAsync( From e465673f5373992a51226eaee45d783632834a78 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:09:04 -0700 Subject: [PATCH 0147/1047] Simplify --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 3 +++ .../Remote/ServiceHub/Host/AssetProvider.cs | 13 +++++-------- .../Host/RemoteWorkspace.SolutionCreator.cs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 1365609946c5a..3476ae1081011 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -25,6 +25,9 @@ internal abstract class AbstractAssetProvider public abstract ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken); public abstract ValueTask GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken); + public async ValueTask GetAssetsAsync(AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) + => await this.GetAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { var solutionCompilationChecksums = await GetAssetAsync(AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 2dc0cf701aafd..e94d9e22601a4 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -100,8 +100,8 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() using var _1 = PooledHashSet.GetInstance(out var checksums); solutionStateChecksum.AnalyzerReferences.AddAllTo(checksums); - await this.SynchronizeAssetsAsync( - assetPath: AssetPathKind.SolutionAnalyzerReferences, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await this.GetAssetsAsync( + assetPath: AssetPathKind.SolutionAnalyzerReferences, checksums, cancellationToken).ConfigureAwait(false); // Note: this search will be optimized on the host side. It will search through the solution level values, // and then the top level project-state-checksum values only. No other project data or document data will be @@ -242,7 +242,7 @@ async Task SynchronizeProjectAssetOrCollectionAsync( foreach (var projectChecksums in allProjectChecksums) addAllChecksums(projectChecksums, checksums, arg); - await SynchronizeAssetsAsync(assetPath, checksums).ConfigureAwait(false); + await GetAssetsAsync(assetPath, checksums, cancellationToken).ConfigureAwait(false); } async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums) @@ -272,13 +272,10 @@ await this.SynchronizeAssetsAsync( + await GetAssetsAsync( assetPath: new(AssetPathKind.DocumentAttributes | AssetPathKind.DocumentText, projectChecksums.ProjectId), - checksums).ConfigureAwait(false); + checksums, cancellationToken).ConfigureAwait(false); } - - async Task SynchronizeAssetsAsync(AssetPath assetPath, HashSet checksums) - => await this.SynchronizeAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); } public async ValueTask SynchronizeAssetsAsync( diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index b78af045a4138..94561b1783ea2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -256,15 +256,15 @@ private async Task UpdateProjectsAsync( foreach (var (_, newProjectChecksums) in newProjectIdToStateChecksums) projectItemChecksums.Add(newProjectChecksums.Info); - await _assetProvider.GetAssetsAsync( - assetPath: AssetPathKind.ProjectAttributes, projectItemChecksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + 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, callback: null, arg: default, cancellationToken).ConfigureAwait(false); + await _assetProvider.GetAssetsAsync( + assetPath: AssetPathKind.ProjectCompilationOptions, projectItemChecksums, cancellationToken).ConfigureAwait(false); } using var _2 = ArrayBuilder.GetInstance(out var projectInfos); From deb601314ddb48872f545bb0571f42fe569fac12 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:11:12 -0700 Subject: [PATCH 0148/1047] Simplify --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 4 ++-- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 3476ae1081011..f05baf4a52fb9 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -23,9 +23,9 @@ internal abstract class AbstractAssetProvider /// return data of type T whose checksum is the given checksum /// public abstract ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken); - public abstract ValueTask GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken); + public abstract Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken); - public async ValueTask GetAssetsAsync(AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) + public async Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) => await this.GetAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index e94d9e22601a4..dccef0e7e1215 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -42,7 +42,7 @@ public override async ValueTask GetAssetAsync( checksums.Add(checksum); using var _2 = ArrayBuilder.GetInstance(1, out var builder); - await this.SynchronizeAssetsAsync>( + await this.GetAssetsAsync>( assetPath, checksums, static (_, asset, builder) => builder.Add(asset), builder, cancellationToken).ConfigureAwait(false); @@ -52,7 +52,7 @@ await this.SynchronizeAssetsAsync>( return builder[0]; } - public override async ValueTask GetAssetsAsync( + public override async Task GetAssetsAsync( AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) where TArg : default { await this.SynchronizeAssetsAsync(assetPath, checksums, callback, arg, cancellationToken).ConfigureAwait(false); @@ -110,7 +110,7 @@ await this.GetAssetsAsync( solutionStateChecksum.Projects.Checksums.AddAllTo(checksums); using var _2 = PooledDictionary.GetInstance(out var checksumToProjectStateChecksums); - await this.SynchronizeAssetsAsync>( + await this.GetAssetsAsync>( assetPath: AssetPathKind.ProjectStateChecksums, checksums, static (checksum, asset, checksumToProjectStateChecksums) => checksumToProjectStateChecksums.Add(checksum, asset), @@ -256,7 +256,7 @@ async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksu // First, fetch all the DocumentStateChecksums for all the documents in the project. using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); - await this.SynchronizeAssetsAsync>( + await this.GetAssetsAsync>( assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), allDocumentStateChecksums, @@ -278,7 +278,7 @@ await GetAssetsAsync( } } - public async ValueTask SynchronizeAssetsAsync( + private async ValueTask SynchronizeAssetsAsync( AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) { Contract.ThrowIfTrue(checksums.Contains(Checksum.Null)); From b1f9ae154508d781530053b11fb47a389eec91a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:20:53 -0700 Subject: [PATCH 0149/1047] Strong typing --- .../Remote/ServiceHub/Host/AssetProvider.cs | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index dccef0e7e1215..0dce52fe6a09e 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -203,7 +203,7 @@ private async Task SynchronizeProjectAssetsWorkerAsync( // Then sync each project's documents in parallel with each other. foreach (var projectChecksums in allProjectChecksums) - tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums)); + tasks.Add(SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false); } @@ -215,12 +215,6 @@ private async Task SynchronizeProjectAssetsWorkerAsync( return; - static void AddAll(HashSet checksums, ChecksumCollection checksumCollection) - { - foreach (var checksum in checksumCollection) - checksums.Add(checksum); - } - Task SynchronizeProjectAssetAsync(AssetPath assetPath, Func getChecksum) => SynchronizeProjectAssetOrCollectionAsync>( assetPath, @@ -230,7 +224,7 @@ Task SynchronizeProjectAssetAsync(AssetPath assetPath, Func(AssetPath assetPath, Func getChecksums) => SynchronizeProjectAssetOrCollectionAsync>( assetPath, - static (projectStateChecksums, checksums, getChecksums) => AddAll(checksums, getChecksums(projectStateChecksums)), + static (projectStateChecksums, checksums, getChecksums) => getChecksums(projectStateChecksums).AddAllTo(checksums), getChecksums); async Task SynchronizeProjectAssetOrCollectionAsync( @@ -244,36 +238,52 @@ async Task SynchronizeProjectAssetOrCollectionAsync( await GetAssetsAsync(assetPath, checksums, cancellationToken).ConfigureAwait(false); } + } + + private async Task SynchronizeProjectDocumentsAsync( + ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) + { + await Task.Yield(); - async Task SynchronizeProjectDocumentsAsync(ProjectStateChecksums projectChecksums) + // First, fetch all the DocumentStateChecksums for all the documents in the project. + using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); { - await Task.Yield(); - using var _1 = PooledHashSet.GetInstance(out var checksums); + using var _2 = PooledHashSet.GetInstance(out var checksums); - AddAll(checksums, projectChecksums.Documents.Checksums); - AddAll(checksums, projectChecksums.AdditionalDocuments.Checksums); - AddAll(checksums, projectChecksums.AnalyzerConfigDocuments.Checksums); + projectChecksums.Documents.Checksums.AddAllTo(checksums); + projectChecksums.AdditionalDocuments.Checksums.AddAllTo(checksums); + projectChecksums.AnalyzerConfigDocuments.Checksums.AddAllTo(checksums); - // First, fetch all the DocumentStateChecksums for all the documents in the project. - using var _2 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); await this.GetAssetsAsync>( assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), allDocumentStateChecksums, cancellationToken).ConfigureAwait(false); + } - // Now go and fetch the info and text for all of those documents. - checksums.Clear(); - foreach (var docChecksums in allDocumentStateChecksums) - { - checksums.Add(docChecksums.Info); - checksums.Add(docChecksums.Text); - } + // Now go and fetch the info and text for all of those documents. + { + using var _2 = ArrayBuilder.GetInstance(out var tasks); + tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentAttributes, static d => d.Info)); + tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentText, static d => d.Text)); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + return; + + async Task GetDocumentItemsAsync( + AssetPathKind assetPathKind, Func getItemChecksum) + { + await Task.Yield(); + using var _ = PooledHashSet.GetInstance(out var checksums); + + foreach (var documentStateChecksums in allDocumentStateChecksums) + checksums.Add(getItemChecksum(documentStateChecksums)); // We know we only need to search the documents in this particular project for those info/text values. So // pass in the right path hint to limit the search on the host side to just the document in this project. - await GetAssetsAsync( - assetPath: new(AssetPathKind.DocumentAttributes | AssetPathKind.DocumentText, projectChecksums.ProjectId), + await GetAssetsAsync( + assetPath: new(assetPathKind, projectChecksums.ProjectId), checksums, cancellationToken).ConfigureAwait(false); } } From 5f637893d3609473ca09adb51eddb136ad3da393 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:27:35 -0700 Subject: [PATCH 0150/1047] Helpers --- .../Remote/Core/AbstractAssetProvider.cs | 43 ++++++++++++++----- .../Remote/ServiceHub/Host/AssetProvider.cs | 9 +--- .../Host/RemoteWorkspace.SolutionCreator.cs | 8 ++-- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index f05baf4a52fb9..90591fd0c3f6c 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -25,9 +25,6 @@ internal abstract class AbstractAssetProvider 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 GetAssetsAsync(AssetPath assetPath, HashSet checksums, CancellationToken cancellationToken) - => await this.GetAssetsAsync(assetPath, checksums, callback: null, arg: default, cancellationToken).ConfigureAwait(false); - public async Task CreateSolutionInfoAsync(Checksum solutionChecksum, CancellationToken cancellationToken) { var solutionCompilationChecksums = await GetAssetAsync(AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); @@ -40,7 +37,7 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) projects.Add(await CreateProjectInfoAsync(projectId, projectChecksum, cancellationToken).ConfigureAwait(false)); - var analyzerReferences = await GetAssetsAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); return SolutionInfo.Create( solutionAttributes.Id, solutionAttributes.Version, solutionAttributes.FilePath, projects.ToImmutableAndClear(), analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); @@ -58,9 +55,9 @@ public async Task CreateProjectInfoAsync(ProjectId projectId, Check await GetAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); var parseOptions = await GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); - var projectReferences = await GetAssetsAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await GetAssetsAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await GetAssetsAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var projectReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); + var metadataReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); + var analyzerReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); var documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); @@ -107,16 +104,42 @@ public async Task CreateDocumentInfoAsync( // TODO: do we need version? return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); } +} + +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); + checksumSet.AddAll(checksums.Children); + + await assetProvider.GetAssetsAsync(assetPath, checksumSet, callback, arg, cancellationToken).ConfigureAwait(false); + } - public async Task> GetAssetsAsync( - AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class + public static async Task> GetAssetsArrayAsync( + this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); using var _2 = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); - await this.GetAssetsAsync>( + await assetProvider.GetAssetsAsync>( assetPath, checksumSet, static (checksum, asset, builder) => builder.Add(asset), builder, diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 0dce52fe6a09e..b6c12e6f484a2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -97,22 +97,17 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() // Ask for solutions and top-level projects as the solution checksums will contain the checksums for // the project states and we want to get that all in one batch. - using var _1 = PooledHashSet.GetInstance(out var checksums); - solutionStateChecksum.AnalyzerReferences.AddAllTo(checksums); - await this.GetAssetsAsync( - assetPath: AssetPathKind.SolutionAnalyzerReferences, checksums, cancellationToken).ConfigureAwait(false); + assetPath: AssetPathKind.SolutionAnalyzerReferences, solutionStateChecksum.AnalyzerReferences, cancellationToken).ConfigureAwait(false); // Note: this search will be optimized on the host side. It will search through the solution level values, // and then the top level project-state-checksum values only. No other project data or document data will be // looked at. - checksums.Clear(); - solutionStateChecksum.Projects.Checksums.AddAllTo(checksums); using var _2 = PooledDictionary.GetInstance(out var checksumToProjectStateChecksums); await this.GetAssetsAsync>( assetPath: AssetPathKind.ProjectStateChecksums, - checksums, + solutionStateChecksum.Projects.Checksums, static (checksum, asset, checksumToProjectStateChecksums) => checksumToProjectStateChecksums.Add(checksum, asset), arg: checksumToProjectStateChecksums, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 94561b1783ea2..1aa80929b316b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -83,7 +83,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { - solution = solution.WithAnalyzerReferences(await _assetProvider.GetAssetsAsync( + solution = solution.WithAnalyzerReferences(await _assetProvider.GetAssetsArrayAsync( AssetPathKind.SolutionAnalyzerReferences, newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } @@ -367,21 +367,21 @@ await _assetProvider.GetAssetAsync( // changed project references if (oldProjectChecksums.ProjectReferences.Checksum != newProjectChecksums.ProjectReferences.Checksum) { - project = project.WithProjectReferences(await _assetProvider.GetAssetsAsync( + 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.GetAssetsAsync( + 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.GetAssetsAsync( + project = project.WithAnalyzerReferences(await _assetProvider.GetAssetsArrayAsync( assetPath: project.Id, newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } From 9ab8c590b78ca9e88b997de0f9b877d4bb9b10be Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:28:57 -0700 Subject: [PATCH 0151/1047] Simplify --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index b6c12e6f484a2..ac6f319f57ded 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -231,7 +231,7 @@ async Task SynchronizeProjectAssetOrCollectionAsync( foreach (var projectChecksums in allProjectChecksums) addAllChecksums(projectChecksums, checksums, arg); - await GetAssetsAsync(assetPath, checksums, cancellationToken).ConfigureAwait(false); + await this.GetAssetsAsync(assetPath, checksums, cancellationToken).ConfigureAwait(false); } } @@ -277,7 +277,7 @@ async Task GetDocumentItemsAsync( // We know we only need to search the documents in this particular project for those info/text values. So // pass in the right path hint to limit the search on the host side to just the document in this project. - await GetAssetsAsync( + await this.GetAssetsAsync( assetPath: new(assetPathKind, projectChecksums.ProjectId), checksums, cancellationToken).ConfigureAwait(false); } From 1089aad0a78683886a5d2c026cd7208e4a5cec39 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:34:19 -0700 Subject: [PATCH 0152/1047] use helpers --- .../Core/Test.Next/Remote/SerializationValidator.cs | 2 +- src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs | 2 +- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index a648ff1d14494..6a53cf42c6826 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -27,7 +27,7 @@ private sealed class AssetProvider(SerializationValidator validator) : AbstractA public override async ValueTask GetAssetAsync(AssetPath assetPath, Checksum checksum, CancellationToken cancellationToken) => await validator.GetValueAsync(checksum).ConfigureAwait(false); - public override async ValueTask GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) where TArg : default + public override async Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) where TArg : default { foreach (var checksum in checksums) { diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index 5824e81d1dcda..30732fa083aa0 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -85,7 +85,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(AssetPath.FullLookupForTesting, new HashSet(map.Keys), callback: null, arg: default, CancellationToken.None); + await service.GetAssetsAsync(AssetPath.FullLookupForTesting, new HashSet(map.Keys), CancellationToken.None); foreach (var kv in map) { diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 90591fd0c3f6c..71a9ee4f74da7 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -126,6 +126,9 @@ 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); From 773779f7bdc4b0c1694692d77eba58916a077ac1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:45:57 -0700 Subject: [PATCH 0153/1047] fix --- src/VisualStudio/Core/Test.Next/TestUtils.cs | 31 ++++++++----------- .../Remote/ServiceHub/Host/AssetProvider.cs | 11 ++++--- 2 files changed, 20 insertions(+), 22 deletions(-) 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/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index ac6f319f57ded..9fbd6c6a83047 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -89,16 +89,19 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() { // first, get top level solution state for the given solution checksum var compilationStateChecksums = await this.GetAssetAsync( - assetPath: AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); // second, get direct children of the solution compilation state. var solutionStateChecksum = await this.GetAssetAsync( - assetPath: AssetPathKind.SolutionStateChecksums, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionStateChecksums, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); + + var sourceGenerationExecutionMap = await this.GetAssetAsync( + AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, compilationStateChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); // Ask for solutions and top-level projects as the solution checksums will contain the checksums for // the project states and we want to get that all in one batch. await this.GetAssetsAsync( - assetPath: AssetPathKind.SolutionAnalyzerReferences, solutionStateChecksum.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + AssetPathKind.SolutionAnalyzerReferences, solutionStateChecksum.AnalyzerReferences, cancellationToken).ConfigureAwait(false); // Note: this search will be optimized on the host side. It will search through the solution level values, // and then the top level project-state-checksum values only. No other project data or document data will be @@ -106,7 +109,7 @@ await this.GetAssetsAsync( using var _2 = PooledDictionary.GetInstance(out var checksumToProjectStateChecksums); await this.GetAssetsAsync>( - assetPath: AssetPathKind.ProjectStateChecksums, + AssetPathKind.ProjectStateChecksums, solutionStateChecksum.Projects.Checksums, static (checksum, asset, checksumToProjectStateChecksums) => checksumToProjectStateChecksums.Add(checksum, asset), arg: checksumToProjectStateChecksums, cancellationToken).ConfigureAwait(false); From 3692a135d33e099d2d65dd8184ab8a225bffd711 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 17:51:29 -0700 Subject: [PATCH 0154/1047] Grab in parallel --- .../Remote/ServiceHub/Host/AssetProvider.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 9fbd6c6a83047..05207518ee705 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -91,17 +91,19 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() var compilationStateChecksums = await this.GetAssetAsync( AssetPathKind.SolutionCompilationStateChecksums, solutionChecksum, cancellationToken).ConfigureAwait(false); - // second, get direct children of the solution compilation state. + // then grab the information we want off of the compilation state object. var solutionStateChecksum = await this.GetAssetAsync( AssetPathKind.SolutionStateChecksums, compilationStateChecksums.SolutionState, cancellationToken).ConfigureAwait(false); - var sourceGenerationExecutionMap = await this.GetAssetAsync( - AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, compilationStateChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); - - // Ask for solutions and top-level projects as the solution checksums will contain the checksums for - // the project states and we want to get that all in one batch. - await this.GetAssetsAsync( - AssetPathKind.SolutionAnalyzerReferences, solutionStateChecksum.AnalyzerReferences, 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); // Note: this search will be optimized on the host side. It will search through the solution level values, // and then the top level project-state-checksum values only. No other project data or document data will be From 3c7f46e050b0366b9f55fb0dcd63e846f84de808 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Apr 2024 11:07:58 +1000 Subject: [PATCH 0155/1047] Serialize VS internal types if that is what we are given --- .../Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index f80277464741f..bf2cb0cda9d77 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -45,7 +45,10 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon } // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL - var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities); + + var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities is VSInternalClientCapabilities internalClientCapabilities + ? internalClientCapabilities + : clientCapabilities); var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager); var requestContext = new RazorCohostRequestContext(context); From 8313794b4141f7312cfe48feaba12f3bd46ba82c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Apr 2024 11:20:47 +1000 Subject: [PATCH 0156/1047] Comments --- .../Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs | 7 +++++++ .../Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs b/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs index fffbe50e64e6d..5984d729b25e6 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/IRazorSemanticTokensRefreshQueue.cs @@ -10,6 +10,13 @@ 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/RazorSemanticTokensRefreshQueueWrapper.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs index fa227306eb6a9..52c7cafc8021c 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorSemanticTokensRefreshQueueWrapper.cs @@ -31,6 +31,9 @@ internal class RazorSemanticTokensRefreshQueueWrapper(SemanticTokensRefreshQueue 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); } From 5049ef4d5ed94f45e7fa0dc225c8c33e94676d8c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 12 Apr 2024 11:34:03 +1000 Subject: [PATCH 0157/1047] Proper serialization PR feedback this time --- .../Cohost/RazorDynamicRegistrationServiceFactory.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index bf2cb0cda9d77..f3f84a2e888e5 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -30,11 +30,16 @@ private class RazorDynamicRegistrationService : ILspService, IOnInitialized { private readonly IRazorCohostDynamicRegistrationService? _dynamicRegistrationService; private readonly IClientLanguageServerManager _clientLanguageServerManager; + private readonly JsonSerializerSettings _serializerSettings; public RazorDynamicRegistrationService(IRazorCohostDynamicRegistrationService? dynamicRegistrationService, IClientLanguageServerManager clientLanguageServerManager) { _dynamicRegistrationService = dynamicRegistrationService; _clientLanguageServerManager = clientLanguageServerManager; + + var serializer = new JsonSerializer(); + serializer.AddVSInternalExtensionConverters(); + _serializerSettings = new JsonSerializerSettings { Converters = serializer.Converters }; } public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) @@ -45,10 +50,7 @@ public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestCon } // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL - - var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities is VSInternalClientCapabilities internalClientCapabilities - ? internalClientCapabilities - : clientCapabilities); + var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, _serializerSettings); var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager); var requestContext = new RazorCohostRequestContext(context); From 0506491eb8eb3d69f8b8d6ec6fc8b2d16fd7fc97 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 19:01:10 -0700 Subject: [PATCH 0158/1047] bulk sync --- .../Remote/Core/AbstractAssetProvider.cs | 57 +++++++++++++++ .../Remote/ServiceHub/Host/AssetProvider.cs | 70 ++++++------------- 2 files changed, 79 insertions(+), 48 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 71a9ee4f74da7..c0cf80530210b 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -59,6 +59,10 @@ public async Task CreateProjectInfoAsync(ProjectId projectId, Check var metadataReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); var analyzerReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectAnalyzerReferences, projectId), projectChecksums.AnalyzerReferences, cancellationToken).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 documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); var analyzerConfigDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments).ConfigureAwait(false); @@ -79,6 +83,11 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId { using var _ = ArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); + await this.GetAssetsAsync( + new(AssetPathKind.DocumentStateChecksums, projectId), + checksumsAndIds.Checksums, + cancellationToken).ConfigureAwait(false); + foreach (var (documentChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); @@ -89,6 +98,54 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId } } + protected async Task SynchronizeProjectDocumentsAsync( + ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) + { + await Task.Yield(); + + // First, fetch all the DocumentStateChecksums for all the documents in the project. + using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); + { + using var _2 = PooledHashSet.GetInstance(out var checksums); + + projectChecksums.Documents.Checksums.AddAllTo(checksums); + projectChecksums.AdditionalDocuments.Checksums.AddAllTo(checksums); + projectChecksums.AnalyzerConfigDocuments.Checksums.AddAllTo(checksums); + + await this.GetAssetsAsync>( + assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, + static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), + allDocumentStateChecksums, + cancellationToken).ConfigureAwait(false); + } + + // Now go and fetch the info and text for all of those documents. + { + using var _2 = ArrayBuilder.GetInstance(out var tasks); + tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentAttributes, static d => d.Info)); + tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentText, static d => d.Text)); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + return; + + async Task GetDocumentItemsAsync( + AssetPathKind assetPathKind, Func getItemChecksum) + { + await Task.Yield(); + using var _ = PooledHashSet.GetInstance(out var checksums); + + foreach (var documentStateChecksums in allDocumentStateChecksums) + checksums.Add(getItemChecksum(documentStateChecksums)); + + // We know we only need to search the documents in this particular project for those info/text values. So + // pass in the right path hint to limit the search on the host side to just the document in this project. + await this.GetAssetsAsync( + assetPath: new(assetPathKind, projectChecksums.ProjectId), + checksums, cancellationToken).ConfigureAwait(false); + } + } + public async Task CreateDocumentInfoAsync( DocumentId documentId, Checksum documentChecksum, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 05207518ee705..7925efc4f8ea2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -23,6 +23,14 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { + private const string s_logFile = @"c:\temp\sync\synclog.txt"; + private static readonly SharedStopwatch s_start = SharedStopwatch.StartNew(); + + static AssetProvider() + { + IOUtilities.PerformIO(() => File.Delete(s_logFile)); + } + private const int PooledChecksumArraySize = 1024; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); @@ -240,57 +248,11 @@ async Task SynchronizeProjectAssetOrCollectionAsync( } } - private async Task SynchronizeProjectDocumentsAsync( - ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) - { - await Task.Yield(); - - // First, fetch all the DocumentStateChecksums for all the documents in the project. - using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); - { - using var _2 = PooledHashSet.GetInstance(out var checksums); - - projectChecksums.Documents.Checksums.AddAllTo(checksums); - projectChecksums.AdditionalDocuments.Checksums.AddAllTo(checksums); - projectChecksums.AnalyzerConfigDocuments.Checksums.AddAllTo(checksums); - - await this.GetAssetsAsync>( - assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, - static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), - allDocumentStateChecksums, - cancellationToken).ConfigureAwait(false); - } - - // Now go and fetch the info and text for all of those documents. - { - using var _2 = ArrayBuilder.GetInstance(out var tasks); - tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentAttributes, static d => d.Info)); - tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentText, static d => d.Text)); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - return; - - async Task GetDocumentItemsAsync( - AssetPathKind assetPathKind, Func getItemChecksum) - { - await Task.Yield(); - using var _ = PooledHashSet.GetInstance(out var checksums); - - foreach (var documentStateChecksums in allDocumentStateChecksums) - checksums.Add(getItemChecksum(documentStateChecksums)); - - // We know we only need to search the documents in this particular project for those info/text values. So - // pass in the right path hint to limit the search on the host side to just the document in this project. - await this.GetAssetsAsync( - assetPath: new(assetPathKind, projectChecksums.ProjectId), - checksums, cancellationToken).ConfigureAwait(false); - } - } - private async ValueTask SynchronizeAssetsAsync( AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) { + // Contract.ThrowIfTrue(typeof(T).Name == "Object"); + Contract.ThrowIfTrue(checksums.Contains(Checksum.Null)); if (checksums.Count == 0) return; @@ -343,6 +305,7 @@ private async ValueTask SynchronizeAssetsAsync( if (missingChecksumsCount > 0) { var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); + var stopwatch = SharedStopwatch.StartNew(); await RequestAssetsAsync( assetPath, missingChecksumsMemory, @@ -358,6 +321,17 @@ await RequestAssetsAsync( }, (this, missingChecksums, callback, arg), cancellationToken).ConfigureAwait(false); + + var time = stopwatch.Elapsed; + var totalTime = s_start.Elapsed; + + IOUtilities.PerformIO(() => + { + lock (this) + { + File.AppendAllText(s_logFile, $"{missingChecksumsCount},{checksums.Count},{time},{totalTime},{typeof(T).Name}\r\n"); + } + }); } } finally From 4aa39d0b13426fe8c70eddb78da40c2c0a3a0b97 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 19:07:06 -0700 Subject: [PATCH 0159/1047] go in parallel --- .../Remote/Core/AbstractAssetProvider.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index c0cf80530210b..9c53a687387c7 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -5,6 +5,7 @@ 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; @@ -33,18 +34,25 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(solutionChecksums.Projects.Length, out var projects); + // Fetch the projects in parallel. + using var _ = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) - projects.Add(await CreateProjectInfoAsync(projectId, projectChecksum, cancellationToken).ConfigureAwait(false)); + projectsTasks.Add(CreateProjectInfoAsync(projectId, projectChecksum, cancellationToken)); var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + 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) { + await Task.Yield(); var projectChecksums = await GetAssetAsync(new(AssetPathKind.ProjectStateChecksums, projectId), projectChecksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(projectId == projectChecksums.ProjectId); @@ -83,11 +91,6 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId { using var _ = ArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); - await this.GetAssetsAsync( - new(AssetPathKind.DocumentStateChecksums, projectId), - checksumsAndIds.Checksums, - cancellationToken).ConfigureAwait(false); - foreach (var (documentChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); From 8530e155b22a3bc95ca7a2277138f6fab125124c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 19:11:22 -0700 Subject: [PATCH 0160/1047] Simplify --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 13 ++++++++----- .../Host/RemoteWorkspace.SolutionCreator.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 9c53a687387c7..02a04e2cab225 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -34,10 +34,13 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + var projectStateChecksums = await this.GetAssetsArrayAsync( + AssetPathKind.ProjectStateChecksums, solutionChecksums.Projects.Checksums, cancellationToken).ConfigureAwait(false); + // Fetch the projects in parallel. using var _ = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); - foreach (var (projectChecksum, projectId) in solutionChecksums.Projects) - projectsTasks.Add(CreateProjectInfoAsync(projectId, projectChecksum, cancellationToken)); + foreach (var projectStateChecksum in projectStateChecksums) + projectsTasks.Add(CreateProjectInfoAsync(projectStateChecksum, cancellationToken)); var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); @@ -50,11 +53,11 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu analyzerReferences).WithTelemetryId(solutionAttributes.TelemetryId); } - public async Task CreateProjectInfoAsync(ProjectId projectId, Checksum projectChecksum, CancellationToken cancellationToken) + public async Task CreateProjectInfoAsync(ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) { await Task.Yield(); - var projectChecksums = await GetAssetAsync(new(AssetPathKind.ProjectStateChecksums, projectId), projectChecksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfFalse(projectId == projectChecksums.ProjectId); + + var projectId = projectChecksums.ProjectId; var attributes = await GetAssetAsync(new(AssetPathKind.ProjectAttributes, projectId), projectChecksums.Info, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(RemoteSupportedLanguages.IsSupported(attributes.Language)); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 1aa80929b316b..6d6d554d0e1a6 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -287,7 +287,7 @@ await _assetProvider.GetAssetsAsync( { // 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(projectId, newProjectChecksums.Checksum, cancellationToken).ConfigureAwait(false); + var projectInfo = await _assetProvider.CreateProjectInfoAsync(newProjectChecksums, cancellationToken).ConfigureAwait(false); projectInfos.Add(projectInfo); } } From 17acbe74abd398c32fc20781f8eda8fbe93bc36f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 19:13:33 -0700 Subject: [PATCH 0161/1047] avoid alloc --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 02a04e2cab225..a992b28d845f1 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -34,12 +34,17 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu var solutionAttributes = await GetAssetAsync(AssetPathKind.SolutionAttributes, solutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); await GetAssetAsync(AssetPathKind.SolutionSourceGeneratorExecutionVersionMap, solutionCompilationChecksums.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); - var projectStateChecksums = await this.GetAssetsArrayAsync( - AssetPathKind.ProjectStateChecksums, solutionChecksums.Projects.Checksums, cancellationToken).ConfigureAwait(false); + using var _1 = ArrayBuilder.GetInstance(solutionChecksums.Projects.Length, out var allProjectStateChecksums); + await this.GetAssetsAsync>( + AssetPathKind.ProjectStateChecksums, + solutionChecksums.Projects.Checksums, + static (_, projectStateChecksums, allProjectStateChecksums) => allProjectStateChecksums.Add(projectStateChecksums), + allProjectStateChecksums, + cancellationToken).ConfigureAwait(false); // Fetch the projects in parallel. - using var _ = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); - foreach (var projectStateChecksum in projectStateChecksums) + using var _2 = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); + foreach (var projectStateChecksum in allProjectStateChecksums) projectsTasks.Add(CreateProjectInfoAsync(projectStateChecksum, cancellationToken)); var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); From cda026c4fcfce414fdedd587fd2588f539ab7ecd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:00:00 -0700 Subject: [PATCH 0162/1047] in progress --- .../Remote/Core/AbstractAssetProvider.cs | 4 +++- .../Host/RemoteWorkspace.SolutionCreator.cs | 20 ++++++++++++++----- .../ServiceHub/Host/SolutionAssetCache.cs | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index a992b28d845f1..f2e3275514851 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -34,6 +34,8 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu 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 gettign 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 allProjectStateChecksums); await this.GetAssetsAsync>( AssetPathKind.ProjectStateChecksums, @@ -109,7 +111,7 @@ async Task> CreateDocumentInfosAsync(ChecksumsAndId } } - protected async Task SynchronizeProjectDocumentsAsync( + public async Task SynchronizeProjectDocumentsAsync( ProjectStateChecksums projectChecksums, CancellationToken cancellationToken) { await Task.Yield(); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 6d6d554d0e1a6..f24e1575f9db4 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -561,11 +561,7 @@ await _assetProvider.GetAssetsAsync 2) - { - using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); - allProjectChecksums.Add(projectChecksums); - await _assetProvider.SynchronizeProjectAssetsAsync(allProjectChecksums, cancellationToken).ConfigureAwait(false); - } + await _assetProvider.SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); return await UpdateDocumentsAsync(project, addDocuments, removeDocuments, oldDocumentIdToStateChecksums, newDocumentIdToStateChecksums, cancellationToken).ConfigureAwait(false); } @@ -615,6 +611,20 @@ private async Task UpdateDocumentsAsync( project = removeDocuments(project.Solution, lazyDocumentsToRemove.ToImmutable()).GetProject(project.Id)!; } + // Bulk get info changes in one go. + using var _ = PooledHashSet.GetInstance(out var infoChecksums); + foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) + { + if (oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums) && + newDocumentChecksums.Info != oldDocumentChecksums.Info) + { + infoChecksums.Add(newDocumentChecksums.Info); + } + } + + await _assetProvider.GetAssetsAsync( + assetPath: AssetPathKind.DocumentAttributes, infoChecksums, cancellationToken).ConfigureAwait(false); + // changed document foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) { diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index 9b5bae5bfb29f..11067ca427033 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -52,7 +52,7 @@ static SolutionAssetCache() /// private readonly TimeSpan _gcAfterTimeSpan; - private readonly ConcurrentDictionary _assets = new(concurrencyLevel: 4, capacity: 10); + private readonly ConcurrentDictionary _assets = new(); private DateTime _lastGCRun; private DateTime _lastActivityTime; From e885d42f7b02510ae4950cbe72b07a2326b9b071 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:08:11 -0700 Subject: [PATCH 0163/1047] no lock --- .../Remote/ServiceHub/Host/AssetProvider.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 7925efc4f8ea2..65c0fa94bf020 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -31,6 +32,21 @@ static AssetProvider() IOUtilities.PerformIO(() => File.Delete(s_logFile)); } + private static readonly AsyncBatchingWorkQueue s_writeQueue = new( + TimeSpan.Zero, + (list, _) => + { + IOUtilities.PerformIO(() => + { + var fullString = string.Join("", list); + File.AppendAllText(s_logFile, fullString); + }); + + return default; + }, + AsynchronousOperationListenerProvider.NullListener, + CancellationToken.None); + private const int PooledChecksumArraySize = 1024; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); @@ -325,13 +341,7 @@ await RequestAssetsAsync( var time = stopwatch.Elapsed; var totalTime = s_start.Elapsed; - IOUtilities.PerformIO(() => - { - lock (this) - { - File.AppendAllText(s_logFile, $"{missingChecksumsCount},{checksums.Count},{time},{totalTime},{typeof(T).Name}\r\n"); - } - }); + s_writeQueue.AddWork($"{missingChecksumsCount},{checksums.Count},{time},{totalTime},{typeof(T).Name}\r\n"); } } finally From dfe5514866acaec29b0d97905cd10fc04e55a9a7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:25:19 -0700 Subject: [PATCH 0164/1047] nodocchecksums --- .../Workspace/Solution/ChecksumsAndIds.cs | 62 +++++++++++++++++ .../Solution/ProjectState_Checksum.cs | 6 +- .../Workspace/Solution/StateChecksums.cs | 24 +++---- .../Workspace/Solution/TextDocumentStates.cs | 25 +++++++ .../Remote/Core/AbstractAssetProvider.cs | 69 +++++++------------ 5 files changed, 128 insertions(+), 58 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs index f144ede095678..05a6905287464 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.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 Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -78,3 +79,64 @@ public bool MoveNext() => (_checksumsAndIds.Checksums.Children[_index], _checksumsAndIds.Ids[_index]); } } + + +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 (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/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/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 0c4478084e0a6..5e7f8edcf9925 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -343,9 +343,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[] { @@ -370,9 +370,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); @@ -392,9 +392,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) @@ -425,9 +425,9 @@ 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; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 0a0b27d970ab8..2fc70c1369ee3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -319,6 +319,31 @@ static async (state, _, cancellationToken) => return new(documentChecksums, SelectAsArray(static s => s.Id)); } + public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) + { + var attributesChecksums = await SelectAsArrayAsync( + static async (state, _, cancellationToken) => + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + return stateChecksums.Info; + }, + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); + var textChecksums = await SelectAsArrayAsync( + static async (state, _, cancellationToken) => + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + return stateChecksums.Text; + }, + arg: default(VoidResult), + cancellationToken).ConfigureAwait(false); + + return new( + new ChecksumCollection(attributesChecksums), + new ChecksumCollection(textChecksums), + SelectAsArray(static s => s.Id)); + } + public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) { // Lazily initialize the file path map if not computed. diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index f2e3275514851..dc325aed91782 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -97,14 +97,14 @@ public async Task CreateProjectInfoAsync(ProjectStateChecksums proj analyzerConfigDocumentInfos, 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); - 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(); @@ -116,57 +116,40 @@ public async Task SynchronizeProjectDocumentsAsync( { await Task.Yield(); - // First, fetch all the DocumentStateChecksums for all the documents in the project. - using var _1 = ArrayBuilder.GetInstance(out var allDocumentStateChecksums); - { - using var _2 = PooledHashSet.GetInstance(out var checksums); + using var _1 = PooledHashSet.GetInstance(out var attributeChecksums); + using var _2 = PooledHashSet.GetInstance(out var textChecksums); - projectChecksums.Documents.Checksums.AddAllTo(checksums); - projectChecksums.AdditionalDocuments.Checksums.AddAllTo(checksums); - projectChecksums.AnalyzerConfigDocuments.Checksums.AddAllTo(checksums); + projectChecksums.Documents.AttributeChecksums.AddAllTo(attributeChecksums); + projectChecksums.AdditionalDocuments.AttributeChecksums.AddAllTo(attributeChecksums); + projectChecksums.AnalyzerConfigDocuments.AttributeChecksums.AddAllTo(attributeChecksums); - await this.GetAssetsAsync>( - assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), checksums, - static (_, documentStateChecksums, allDocumentStateChecksums) => allDocumentStateChecksums.Add(documentStateChecksums), - allDocumentStateChecksums, - cancellationToken).ConfigureAwait(false); - } + projectChecksums.Documents.TextChecksums.AddAllTo(textChecksums); + projectChecksums.AdditionalDocuments.TextChecksums.AddAllTo(textChecksums); + projectChecksums.AnalyzerConfigDocuments.TextChecksums.AddAllTo(textChecksums); - // Now go and fetch the info and text for all of those documents. - { - using var _2 = ArrayBuilder.GetInstance(out var tasks); - tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentAttributes, static d => d.Info)); - tasks.Add(GetDocumentItemsAsync(AssetPathKind.DocumentText, static d => d.Text)); - await Task.WhenAll(tasks).ConfigureAwait(false); - } + using var _ = ArrayBuilder.GetInstance(2, out var tasks); - return; + tasks.Add(this.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), + attributeChecksums, + cancellationToken)); - async Task GetDocumentItemsAsync( - AssetPathKind assetPathKind, Func getItemChecksum) - { - await Task.Yield(); - using var _ = PooledHashSet.GetInstance(out var checksums); - - foreach (var documentStateChecksums in allDocumentStateChecksums) - checksums.Add(getItemChecksum(documentStateChecksums)); + tasks.Add(this.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), + textChecksums, + cancellationToken)); - // We know we only need to search the documents in this particular project for those info/text values. So - // pass in the right path hint to limit the search on the host side to just the document in this project. - await this.GetAssetsAsync( - assetPath: new(assetPathKind, projectChecksums.ProjectId), - checksums, cancellationToken).ConfigureAwait(false); - } + await Task.WhenAll(tasks).ConfigureAwait(false); } public async Task CreateDocumentInfoAsync( - DocumentId documentId, Checksum documentChecksum, CancellationToken cancellationToken) + DocumentId documentId, Checksum attributeChecksum, Checksum textChecksum, CancellationToken cancellationToken) { - var documentSnapshot = await GetAssetAsync(new(AssetPathKind.DocumentStateChecksums, documentId), documentChecksum, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); + //var documentSnapshot = await GetAssetAsync(new(AssetPathKind.DocumentStateChecksums, documentId), documentChecksum, cancellationToken).ConfigureAwait(false); + //Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); - var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), documentSnapshot.Info, cancellationToken).ConfigureAwait(false); - var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), documentSnapshot.Text, cancellationToken).ConfigureAwait(false); + var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), attributeChecksum, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), textChecksum, cancellationToken).ConfigureAwait(false); var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); var textLoader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), attributes.FilePath)); From 89fd388bbea41b9eb9a9c5ad8429c86e49ef8d5a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:29:03 -0700 Subject: [PATCH 0165/1047] in progress --- .../Host/RemoteWorkspace.SolutionCreator.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index f24e1575f9db4..1fc58638669c3 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -499,27 +499,28 @@ 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 = PooledDictionary.GetInstance(out var oldDocumentIdToChecksum); - using var _2 = PooledDictionary.GetInstance(out var newDocumentIdToChecksum); + using var _1 = PooledDictionary.GetInstance(out var oldDocumentIdToChecksum); + using var _2 = PooledDictionary.GetInstance(out var newDocumentIdToChecksum); - foreach (var (oldChecksum, documentId) in oldChecksums) - oldDocumentIdToChecksum.Add(documentId, oldChecksum); + foreach (var (oldAttributeChecksum, oldTextChecksum, documentId) in oldChecksums) + oldDocumentIdToChecksum.Add(documentId, (oldAttributeChecksum, oldTextChecksum)); - foreach (var (newChecksum, documentId) in newChecksums) - newDocumentIdToChecksum.Add(documentId, newChecksum); + foreach (var (newAttributeChecksum, newTextChecksum, documentId) in newChecksums) + newDocumentIdToChecksum.Add(documentId, (newAttributeChecksum, newTextChecksum)); // 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 (oldChecksum, documentId) in oldChecksums) + foreach (var (oldAttributeChecksum, oldTextChecksum, documentId) in oldChecksums) { if (newDocumentIdToChecksum.TryGetValue(documentId, out var newChecksum) && - oldChecksum == newChecksum) + oldAttributeChecksum == newChecksum.attributeChecksum && + oldTextChecksum == newChecksum.textChecksum) { oldDocumentIdToChecksum.Remove(documentId); newDocumentIdToChecksum.Remove(documentId); From 71d518fd2fb4f5d2924ef0fbae2f777088102e7a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:39:47 -0700 Subject: [PATCH 0166/1047] in progress --- .../Remote/Core/AbstractAssetProvider.cs | 3 - .../Host/RemoteWorkspace.SolutionCreator.cs | 109 ++++++------------ 2 files changed, 34 insertions(+), 78 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index dc325aed91782..3b145bd9535cf 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -145,9 +145,6 @@ public async Task SynchronizeProjectDocumentsAsync( public async Task CreateDocumentInfoAsync( DocumentId documentId, Checksum attributeChecksum, Checksum textChecksum, CancellationToken cancellationToken) { - //var documentSnapshot = await GetAssetAsync(new(AssetPathKind.DocumentStateChecksums, documentId), documentChecksum, cancellationToken).ConfigureAwait(false); - //Contract.ThrowIfTrue(documentId != documentSnapshot.DocumentId); - var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), attributeChecksum, cancellationToken).ConfigureAwait(false); var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), textChecksum, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 1fc58638669c3..d65562f56b02c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -388,10 +388,8 @@ await _assetProvider.GetAssetAsync( // 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), @@ -402,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), @@ -416,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), @@ -497,82 +491,57 @@ private async Task UpdateProjectInfoAsync(Project project, Checksum inf private async Task UpdateDocumentsAsync( Project project, - ProjectStateChecksums projectChecksums, - TextDocumentStates existingTextDocumentStates, DocumentChecksumsAndIds oldChecksums, DocumentChecksumsAndIds newChecksums, Func, Solution> addDocuments, Func, Solution> removeDocuments, CancellationToken cancellationToken) where TDocumentState : TextDocumentState { - using var _1 = PooledDictionary.GetInstance(out var oldDocumentIdToChecksum); - using var _2 = PooledDictionary.GetInstance(out var newDocumentIdToChecksum); + using var _1 = PooledDictionary.GetInstance(out var oldDocumentIdToChecksums); + using var _2 = PooledDictionary.GetInstance(out var newDocumentIdToChecksums); foreach (var (oldAttributeChecksum, oldTextChecksum, documentId) in oldChecksums) - oldDocumentIdToChecksum.Add(documentId, (oldAttributeChecksum, oldTextChecksum)); + oldDocumentIdToChecksums.Add(documentId, (oldAttributeChecksum, oldTextChecksum)); foreach (var (newAttributeChecksum, newTextChecksum, documentId) in newChecksums) - newDocumentIdToChecksum.Add(documentId, (newAttributeChecksum, newTextChecksum)); + newDocumentIdToChecksums.Add(documentId, (newAttributeChecksum, newTextChecksum)); // 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) { - if (newDocumentIdToChecksum.TryGetValue(documentId, out var newChecksum) && + if (newDocumentIdToChecksums.TryGetValue(documentId, out var newChecksum) && oldAttributeChecksum == newChecksum.attributeChecksum && oldTextChecksum == newChecksum.textChecksum) { - oldDocumentIdToChecksum.Remove(documentId); - newDocumentIdToChecksum.Remove(documentId); + oldDocumentIdToChecksums.Remove(documentId); + newDocumentIdToChecksums.Remove(documentId); } } - using var _3 = PooledDictionary.GetInstance(out var oldDocumentIdToStateChecksums); - using var _4 = PooledDictionary.GetInstance(out var newDocumentIdToStateChecksums); - - // Now, find the full state checksums for all the old documents - foreach (var (documentId, oldChecksum) in oldDocumentIdToChecksum) - { - // this should be cheap since we already computed oldSolutionChecksums (which calls into this). - var oldDocumentStateChecksums = await existingTextDocumentStates - .GetRequiredState(documentId) - .GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(oldDocumentStateChecksums.DocumentId != documentId); - Contract.ThrowIfTrue(oldDocumentStateChecksums.Checksum != oldChecksum); - - oldDocumentIdToStateChecksums.Add(documentId, oldDocumentStateChecksums); - } - // 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(newDocumentIdToChecksum.Values); + newChecksumsToSync.AddRange(newDocumentIdToChecksums.Values.Select(v => v.attributeChecksum)); - await _assetProvider.GetAssetsAsync>( - assetPath: AssetPath.DocumentsInProject(project.Id), newChecksumsToSync, - static (checksum, documentStateChecksum, newDocumentIdToStateChecksums) => - { - Contract.ThrowIfTrue(checksum != documentStateChecksum.Checksum); - newDocumentIdToStateChecksums.Add(documentStateChecksum.DocumentId, documentStateChecksum); - }, - arg: newDocumentIdToStateChecksums, - cancellationToken).ConfigureAwait(false); + await _assetProvider.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentAttributes, project.Id), newChecksumsToSync, cancellationToken).ConfigureAwait(false); + + newChecksumsToSync.Clear(); + newChecksumsToSync.AddRange(newDocumentIdToChecksums.Values.Select(v => v.textChecksum)); - // 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) - await _assetProvider.SynchronizeProjectDocumentsAsync(projectChecksums, cancellationToken).ConfigureAwait(false); + await _assetProvider.GetAssetsAsync( + assetPath: new(AssetPathKind.DocumentText, project.Id), newChecksumsToSync, cancellationToken).ConfigureAwait(false); - return await UpdateDocumentsAsync(project, addDocuments, removeDocuments, oldDocumentIdToStateChecksums, newDocumentIdToStateChecksums, 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, + Dictionary oldDocumentIdToStateChecksums, + Dictionary newDocumentIdToStateChecksums, CancellationToken cancellationToken) { // added document @@ -585,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); } } @@ -612,29 +581,15 @@ private async Task UpdateDocumentsAsync( project = removeDocuments(project.Solution, lazyDocumentsToRemove.ToImmutable()).GetProject(project.Id)!; } - // Bulk get info changes in one go. - using var _ = PooledHashSet.GetInstance(out var infoChecksums); - foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) - { - if (oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums) && - newDocumentChecksums.Info != oldDocumentChecksums.Info) - { - infoChecksums.Add(newDocumentChecksums.Info); - } - } - - await _assetProvider.GetAssetsAsync( - assetPath: AssetPathKind.DocumentAttributes, infoChecksums, cancellationToken).ConfigureAwait(false); - // changed document foreach (var (documentId, newDocumentChecksums) in newDocumentIdToStateChecksums) { if (!oldDocumentIdToStateChecksums.TryGetValue(documentId, out var oldDocumentChecksums)) - { continue; - } - Contract.ThrowIfTrue(oldDocumentChecksums.Checksum == newDocumentChecksums.Checksum); + Contract.ThrowIfTrue( + oldDocumentChecksums.attributeChecksum == newDocumentChecksums.attributeChecksum && + oldDocumentChecksums.textChecksum == newDocumentChecksums.textChecksum); var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); Contract.ThrowIfNull(document); @@ -645,19 +600,23 @@ private async Task UpdateDocumentsAsync( 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( - assetPath: document.Id, newDocumentChecksums.Text, cancellationToken).ConfigureAwait(false); + assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); document = document.Kind switch From beba334c790e12666323611c9db52c6271b7996b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:44:36 -0700 Subject: [PATCH 0167/1047] in progress --- .../Remote/WellKnownSynchronizationKind.cs | 1 - .../Serialization/SerializationExtensions.cs | 1 - .../Serialization/SerializerService.cs | 5 ----- .../Portable/Workspace/Solution/AssetPath.cs | 8 +++---- .../Workspace/Solution/StateChecksums.cs | 21 ------------------- .../Remote/Core/AbstractAssetProvider.cs | 4 ++-- 6 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs index 9d5ac26c44b1d..e54361ae4ebd0 100644 --- a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs +++ b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs @@ -14,7 +14,6 @@ internal enum WellKnownSynchronizationKind // Solution snapshot state, only referencing actual user (non-generated) documents, options, and references. SolutionState, ProjectState, - DocumentState, ChecksumCollection, diff --git a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs index 90081918ab1b7..07b6f5853642b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs @@ -18,7 +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, diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 0b06f7cebbc98..226165867e859 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -157,10 +157,6 @@ 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); return; @@ -188,7 +184,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), diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs index 38de8a081c3ef..85b8d73952855 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AssetPath.cs @@ -76,7 +76,6 @@ public AssetPath(AssetPathKind kind, DocumentId? documentId) public bool IncludeProjectMetadataReferences => (_kind & AssetPathKind.ProjectMetadataReferences) != 0; public bool IncludeProjectAnalyzerReferences => (_kind & AssetPathKind.ProjectAnalyzerReferences) != 0; - public bool IncludeDocumentStateChecksums => (_kind & AssetPathKind.DocumentStateChecksums) != 0; public bool IncludeDocumentAttributes => (_kind & AssetPathKind.DocumentAttributes) != 0; public bool IncludeDocumentText => (_kind & AssetPathKind.DocumentText) != 0; @@ -132,9 +131,8 @@ internal enum AssetPathKind ProjectAnalyzerReferences = 1 << 21, // Keep a gap so we can easily add more project kinds - DocumentStateChecksums = 1 << 25, - DocumentAttributes = 1 << 26, - DocumentText = 1 << 27, + DocumentAttributes = 1 << 25, + DocumentText = 1 << 26, /// /// Search solution-compilation-state level information. @@ -154,5 +152,5 @@ internal enum AssetPathKind /// /// Search documents for results. /// - Documents = DocumentStateChecksums | DocumentAttributes | DocumentText, + Documents = DocumentAttributes | DocumentText, } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 5e7f8edcf9925..161d55e65cc93 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -491,30 +491,12 @@ 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 void AddAllTo(HashSet checksums) { - checksums.AddIfNotNullChecksum(this.Checksum); checksums.AddIfNotNullChecksum(this.Info); checksums.AddIfNotNullChecksum(this.Text); } - public static DocumentStateChecksums Deserialize(ObjectReader reader) - { - return new DocumentStateChecksums( - documentId: DocumentId.ReadFrom(reader), - infoChecksum: Checksum.ReadFrom(reader), - textChecksum: Checksum.ReadFrom(reader)); - } - public async Task FindAsync( AssetPath assetPath, TextDocumentState state, @@ -526,9 +508,6 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); - if (assetPath.IncludeDocumentStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; - if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) result[Info] = state.Attributes; diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 3b145bd9535cf..98753ee1191d0 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -130,12 +130,12 @@ public async Task SynchronizeProjectDocumentsAsync( using var _ = ArrayBuilder.GetInstance(2, out var tasks); tasks.Add(this.GetAssetsAsync( - assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), + assetPath: new(AssetPathKind.DocumentAttributes, projectChecksums.ProjectId), attributeChecksums, cancellationToken)); tasks.Add(this.GetAssetsAsync( - assetPath: new(AssetPathKind.DocumentStateChecksums, projectChecksums.ProjectId), + assetPath: new(AssetPathKind.DocumentText, projectChecksums.ProjectId), textChecksums, cancellationToken)); From 5d6dde80183269d50a496a180774bc522d885f98 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:52:01 -0700 Subject: [PATCH 0168/1047] in progress --- .../Remote/SerializationValidator.cs | 30 ++++++------------- .../Remote/ServiceHub/Host/TestUtils.cs | 16 ++-------- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 6a53cf42c6826..3697752ee3802 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -174,36 +174,24 @@ 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, + textChecksum, WellKnownSynchronizationKind.SerializableSourceText, (v, k, s) => new SolutionAsset(s.CreateChecksum(v, CancellationToken.None), v)); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index c1f05df5cf279..2591d975938a0 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -128,24 +128,14 @@ async Task> GetAllChildrenChecksumsAsync(Checksum solutionChec 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( - assetPath: documentId, documentChecksum, CancellationToken.None).ConfigureAwait(false); - AddAllTo(documentChecksums, checksums); - } - } - #else // have this to avoid error on async From fe40b2d526f35bd2cdf5c486c40997d7bfd45734 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 20:56:32 -0700 Subject: [PATCH 0169/1047] in progress --- .../Remote/SerializationValidator.cs | 51 +++++++------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 3697752ee3802..b8f4c75cf1aae 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -122,9 +122,6 @@ public async Task GetSolutionAsync(SolutionAssetStorage.Scope scope) public ChecksumObjectCollection ToProjectObjects(ChecksumCollection collection) => new(this, collection); - public ChecksumObjectCollection ToDocumentObjects(ChecksumCollection collection) - => new(this, collection); - internal async Task VerifyAssetAsync(SolutionStateChecksums solutionObject) { await VerifyAssetSerializationAsync( @@ -177,10 +174,10 @@ await VerifyAssetSerializationAsync( foreach (var (attributeChecksum, textChecksum, documentId) in projectObject.Documents) await VerifyAssetAsync(attributeChecksum, textChecksum).ConfigureAwait(false); - foreach (var(attributeChecksum, textChecksum, documentId) in projectObject.AdditionalDocuments) + foreach (var (attributeChecksum, textChecksum, documentId) in projectObject.AdditionalDocuments) await VerifyAssetAsync(attributeChecksum, textChecksum).ConfigureAwait(false); - foreach (var(attributeChecksum, textChecksum, documentId) in projectObject.AnalyzerConfigDocuments) + foreach (var (attributeChecksum, textChecksum, documentId) in projectObject.AnalyzerConfigDocuments) await VerifyAssetAsync(attributeChecksum, textChecksum).ConfigureAwait(false); } @@ -239,6 +236,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); @@ -261,7 +267,7 @@ internal void SolutionStateEqual(SolutionStateChecksums solutionObject1, Solutio 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); @@ -270,23 +276,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)); + AssertDocumentChecksumCollectionEqual(projectObjects1.Documents, projectObjects2.Documents); + AssertDocumentChecksumCollectionEqual(projectObjects1.AdditionalDocuments, projectObjects2.AdditionalDocuments); + AssertDocumentChecksumCollectionEqual(projectObjects1.AnalyzerConfigDocuments, projectObjects2.AnalyzerConfigDocuments); } - 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); - } - - private void ProjectStatesEqual(ChecksumObjectCollection projectObjects1, ChecksumObjectCollection projectObjects2) + private static void ProjectStatesEqual(ChecksumObjectCollection projectObjects1, ChecksumObjectCollection projectObjects2) { SynchronizationObjectEqual(projectObjects1, projectObjects2); @@ -296,16 +291,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, @@ -319,13 +304,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) From 8ed47cef32a8bb993031988db0dd91c4d9e14f18 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 21:12:19 -0700 Subject: [PATCH 0170/1047] Simplify --- .../Remote/ServiceHub/Host/AssetProvider.cs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 65c0fa94bf020..5c8077b622291 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -129,23 +129,14 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() AssetPathKind.SolutionAnalyzerReferences, solutionStateChecksum.AnalyzerReferences, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false); - // Note: this search will be optimized on the host side. It will search through the solution level values, - // and then the top level project-state-checksum values only. No other project data or document data will be - // looked at. - - using var _2 = PooledDictionary.GetInstance(out var checksumToProjectStateChecksums); - await this.GetAssetsAsync>( + using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); + await this.GetAssetsAsync>( AssetPathKind.ProjectStateChecksums, solutionStateChecksum.Projects.Checksums, - static (checksum, asset, checksumToProjectStateChecksums) => checksumToProjectStateChecksums.Add(checksum, asset), - arg: checksumToProjectStateChecksums, cancellationToken).ConfigureAwait(false); - - using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); + static (_, asset, allProjectStateChecksums) => allProjectStateChecksums.Add(asset), + arg: allProjectStateChecksums, cancellationToken).ConfigureAwait(false); // fourth, get all projects and documents in the solution - foreach (var (_, projectStateChecksums) in checksumToProjectStateChecksums) - allProjectStateChecksums.Add(projectStateChecksums); - await SynchronizeProjectAssetsAsync(allProjectStateChecksums, cancellationToken).ConfigureAwait(false); } } From a9c08987ed2f75ae039fe1f084ae248fb158a51a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 21:13:13 -0700 Subject: [PATCH 0171/1047] Remove --- .../Remote/ServiceHub/Host/AssetProvider.cs | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 5c8077b622291..74ff037246cfa 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -24,29 +24,6 @@ namespace Microsoft.CodeAnalysis.Remote; internal sealed partial class AssetProvider(Checksum solutionChecksum, SolutionAssetCache assetCache, IAssetSource assetSource, ISerializerService serializerService) : AbstractAssetProvider { - private const string s_logFile = @"c:\temp\sync\synclog.txt"; - private static readonly SharedStopwatch s_start = SharedStopwatch.StartNew(); - - static AssetProvider() - { - IOUtilities.PerformIO(() => File.Delete(s_logFile)); - } - - private static readonly AsyncBatchingWorkQueue s_writeQueue = new( - TimeSpan.Zero, - (list, _) => - { - IOUtilities.PerformIO(() => - { - var fullString = string.Join("", list); - File.AppendAllText(s_logFile, fullString); - }); - - return default; - }, - AsynchronousOperationListenerProvider.NullListener, - CancellationToken.None); - private const int PooledChecksumArraySize = 1024; private static readonly ObjectPool s_checksumPool = new(() => new Checksum[PooledChecksumArraySize], 16); @@ -312,7 +289,6 @@ private async ValueTask SynchronizeAssetsAsync( if (missingChecksumsCount > 0) { var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); - var stopwatch = SharedStopwatch.StartNew(); await RequestAssetsAsync( assetPath, missingChecksumsMemory, @@ -328,11 +304,6 @@ await RequestAssetsAsync( }, (this, missingChecksums, callback, arg), cancellationToken).ConfigureAwait(false); - - var time = stopwatch.Elapsed; - var totalTime = s_start.Elapsed; - - s_writeQueue.AddWork($"{missingChecksumsCount},{checksums.Count},{time},{totalTime},{typeof(T).Name}\r\n"); } } finally From cb5cb357b41bdf70cd2f4d4717eb8364f3fa4c45 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 21:16:06 -0700 Subject: [PATCH 0172/1047] Update src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs --- .../Core/Portable/Workspace/Solution/ChecksumsAndIds.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs index 05a6905287464..474b4bb7e2f1a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs @@ -80,7 +80,6 @@ public bool MoveNext() } } - internal readonly struct DocumentChecksumsAndIds { public readonly Checksum Checksum; From 9d0cdd86fce55cfeb9ee87e75036dedf83c69f78 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 21:18:25 -0700 Subject: [PATCH 0173/1047] kill --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 74ff037246cfa..06c57485f348a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -235,8 +235,6 @@ async Task SynchronizeProjectAssetOrCollectionAsync( private async ValueTask SynchronizeAssetsAsync( AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) { - // Contract.ThrowIfTrue(typeof(T).Name == "Object"); - Contract.ThrowIfTrue(checksums.Contains(Checksum.Null)); if (checksums.Count == 0) return; From b12ef45a84eb680584892e77e5c9fb95f102bfc9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 21:39:30 -0700 Subject: [PATCH 0174/1047] Simplify --- .../Workspace/Solution/TextDocumentStates.cs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 2fc70c1369ee3..713a76b23060b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -321,26 +321,19 @@ static async (state, _, cancellationToken) => public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - var attributesChecksums = await SelectAsArrayAsync( - static async (state, _, cancellationToken) => - { - var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - return stateChecksums.Info; - }, - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); - var textChecksums = await SelectAsArrayAsync( - static async (state, _, cancellationToken) => - { - var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - return stateChecksums.Text; - }, - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); + using var _1 = ArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); + using var _2 = ArrayBuilder.GetInstance(_map.Count, out var textChecksums); + + foreach (var (_, state) in _map) + { + var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + attributeChecksums.Add(stateChecksums.Info); + textChecksums.Add(stateChecksums.Text); + } return new( - new ChecksumCollection(attributesChecksums), - new ChecksumCollection(textChecksums), + new ChecksumCollection(attributeChecksums.ToImmutableAndClear()), + new ChecksumCollection(textChecksums.ToImmutableAndClear()), SelectAsArray(static s => s.Id)); } From 32174387a1f55790ff52de5f1ff568b5d2cdfc24 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 11 Apr 2024 21:47:59 -0700 Subject: [PATCH 0175/1047] Simplify --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 98753ee1191d0..50b8520138855 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -127,19 +127,17 @@ public async Task SynchronizeProjectDocumentsAsync( projectChecksums.AdditionalDocuments.TextChecksums.AddAllTo(textChecksums); projectChecksums.AnalyzerConfigDocuments.TextChecksums.AddAllTo(textChecksums); - using var _ = ArrayBuilder.GetInstance(2, out var tasks); - - tasks.Add(this.GetAssetsAsync( + var attributesTask = this.GetAssetsAsync( assetPath: new(AssetPathKind.DocumentAttributes, projectChecksums.ProjectId), attributeChecksums, - cancellationToken)); + cancellationToken); - tasks.Add(this.GetAssetsAsync( + var textTask = this.GetAssetsAsync( assetPath: new(AssetPathKind.DocumentText, projectChecksums.ProjectId), textChecksums, - cancellationToken)); + cancellationToken); - await Task.WhenAll(tasks).ConfigureAwait(false); + await Task.WhenAll(attributesTask, textTask).ConfigureAwait(false); } public async Task CreateDocumentInfoAsync( From 8bb9fcbcf269c765453748a7c40472a8fcc176e6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 12 Apr 2024 12:15:46 +0000 Subject: [PATCH 0176/1047] Update dependencies from https://github.com/dotnet/source-build-externals build 20240411.1 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 9.0.0-alpha.1.24208.1 -> To Version 9.0.0-alpha.1.24211.1 --- eng/Version.Details.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index cc1e1981a5841..3c503314c050b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,9 +2,9 @@ - + https://github.com/dotnet/source-build-externals - be742e4d97b36d6b2ce521938aa81988fbea7b6a + 83566118e44922c30d146654d42c7c3745cc119d From 4bf115dade465def75f49ba4f2e053a50cf80285 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 12 Apr 2024 12:17:09 +0000 Subject: [PATCH 0177/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240409.3 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520903 From d414ddb40d59b584d9fbd5139786cf447db5a4a6 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Fri, 12 Apr 2024 10:38:55 -0700 Subject: [PATCH 0178/1047] Use the new Solution.AddProjects instead of adding projects to the solution individually (#72994) Prevents some solution forking --- .../Core/Portable/Workspace/Workspace.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 024bdde79a748..201d28946e432 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -636,6 +636,18 @@ protected virtual void Dispose(bool finalize) #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); @@ -654,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); @@ -671,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); From 7f36e6fcb608e5cc0650ea713ffe1dbbbd557437 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 10:40:51 -0700 Subject: [PATCH 0179/1047] Use a frozendictionary on net-core and a readonly one on netfx --- .../Workspace/Solution/TextDocumentStates.cs | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 0a0b27d970ab8..9144889c5f469 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -6,6 +6,7 @@ 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; @@ -19,16 +20,29 @@ 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 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), FrozenDictionary>.Empty); + new([], + ImmutableSortedDictionary.Create(DocumentIdComparer.Instance), + filePathToDocumentIds: null); private readonly ImmutableList _ids; @@ -39,12 +53,12 @@ internal sealed class TextDocumentStates /// private readonly ImmutableSortedDictionary _map; - private FrozenDictionary>? _filePathToDocumentIds; + private FilePathToDocumentIds? _filePathToDocumentIds; private TextDocumentStates( ImmutableList ids, ImmutableSortedDictionary map, - FrozenDictionary>? filePathToDocumentIds) + FilePathToDocumentIds? filePathToDocumentIds) { Debug.Assert(map.KeyComparer == DocumentIdComparer.Instance); @@ -342,10 +356,14 @@ public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryA : null; } - private FrozenDictionary> ComputeFilePathToDocumentIds() + private FilePathToDocumentIds ComputeFilePathToDocumentIds() { - using var pooledDictionary = s_filePathPool.GetPooledObject(); - var result = pooledDictionary.Object; +#if NET + using var pooledObject = s_filePathPool.GetPooledObject(); + var dictionary = pooledObject.Object; +#else + var dictionary = new Dictionary>(SolutionState.FilePathComparer); +#endif foreach (var (documentId, state) in _map) { @@ -353,11 +371,15 @@ private FrozenDictionary> ComputeFilePathToDocumen if (filePath is null) continue; - result[filePath] = result.TryGetValue(filePath, out var existingValue) + dictionary[filePath] = dictionary.TryGetValue(filePath, out var existingValue) ? existingValue.Add(documentId) : OneOrMany.Create(documentId); } - return result.ToFrozenDictionary(SolutionState.FilePathComparer); +#if NET + return dictionary.ToFrozenDictionary(SolutionState.FilePathComparer); +#else + return new(dictionary); +#endif } } From ad635bd49b069ac22733fc810cad88692c0e63bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 10:42:33 -0700 Subject: [PATCH 0180/1047] Restore --- .../Portable/Workspace/Solution/TextDocumentStates.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 9144889c5f469..f7b1cbdba3b65 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -360,9 +360,9 @@ private FilePathToDocumentIds ComputeFilePathToDocumentIds() { #if NET using var pooledObject = s_filePathPool.GetPooledObject(); - var dictionary = pooledObject.Object; + var result = pooledObject.Object; #else - var dictionary = new Dictionary>(SolutionState.FilePathComparer); + var result = new Dictionary>(SolutionState.FilePathComparer); #endif foreach (var (documentId, state) in _map) @@ -371,15 +371,15 @@ private FilePathToDocumentIds ComputeFilePathToDocumentIds() if (filePath is null) continue; - dictionary[filePath] = dictionary.TryGetValue(filePath, out var existingValue) + result[filePath] = result.TryGetValue(filePath, out var existingValue) ? existingValue.Add(documentId) : OneOrMany.Create(documentId); } #if NET - return dictionary.ToFrozenDictionary(SolutionState.FilePathComparer); + return result.ToFrozenDictionary(SolutionState.FilePathComparer); #else - return new(dictionary); + return new(result); #endif } } From eb630b28ada0f929309a4d76e3dfe4e3fc93b1b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 10:42:52 -0700 Subject: [PATCH 0181/1047] Restore --- .../Core/Portable/Workspace/Solution/TextDocumentStates.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index f7b1cbdba3b65..fddbe7d86e0b4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -359,8 +359,8 @@ public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryA private FilePathToDocumentIds ComputeFilePathToDocumentIds() { #if NET - using var pooledObject = s_filePathPool.GetPooledObject(); - var result = pooledObject.Object; + using var pooledDictionary = s_filePathPool.GetPooledObject(); + var result = pooledDictionary.Object; #else var result = new Dictionary>(SolutionState.FilePathComparer); #endif From 086fe84a08efa1281a79b9521cd0dd96c4a91ad3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 10:44:33 -0700 Subject: [PATCH 0182/1047] Customize --- .../Core/Portable/Workspace/Solution/TextDocumentStates.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index fddbe7d86e0b4..4935978fddfce 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -42,7 +42,11 @@ internal sealed class TextDocumentStates public static readonly TextDocumentStates Empty = new([], ImmutableSortedDictionary.Create(DocumentIdComparer.Instance), - filePathToDocumentIds: null); +#if NET + FilePathToDocumentIds.Empty); +#else + new(new Dictionary>())); +#endif private readonly ImmutableList _ids; From 051ac82ebb5ab9ecb9a4ca9257328cba18277ba9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:03:43 -0700 Subject: [PATCH 0183/1047] Spelling --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 50b8520138855..33b88c0d515d4 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -34,7 +34,7 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu 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 gettign all the data in a single call, and + // 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 allProjectStateChecksums); await this.GetAssetsAsync>( From 17c6ce773f62ad328e39314f0b0228e1ef244486 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:08:48 -0700 Subject: [PATCH 0184/1047] Simplify --- .../Remote/Core/AbstractAssetProvider.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 33b88c0d515d4..2bc5d7ac459ef 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -36,19 +36,15 @@ public async Task CreateSolutionInfoAsync(Checksum solutionChecksu // 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 allProjectStateChecksums); - await this.GetAssetsAsync>( + using var _1 = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); + await this.GetAssetHelper().GetAssetsAsync( AssetPathKind.ProjectStateChecksums, solutionChecksums.Projects.Checksums, - static (_, projectStateChecksums, allProjectStateChecksums) => allProjectStateChecksums.Add(projectStateChecksums), - allProjectStateChecksums, + static (_, projectStateChecksums, tuple) => tuple.projectsTasks.Add(tuple.@this.CreateProjectInfoAsync(projectStateChecksums, tuple.cancellationToken)), + (@this: this, projectsTasks, cancellationToken), cancellationToken).ConfigureAwait(false); // Fetch the projects in parallel. - using var _2 = ArrayBuilder>.GetInstance(solutionChecksums.Projects.Length, out var projectsTasks); - foreach (var projectStateChecksum in allProjectStateChecksums) - projectsTasks.Add(CreateProjectInfoAsync(projectStateChecksum, cancellationToken)); - var analyzerReferences = await this.GetAssetsArrayAsync(AssetPathKind.SolutionAnalyzerReferences, solutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false); var projects = await Task.WhenAll(projectsTasks).ConfigureAwait(false); @@ -152,6 +148,18 @@ public async Task CreateDocumentInfoAsync( // TODO: do we need version? return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); } + + public AssetHelper GetAssetHelper() + => new(this); + + public readonly struct AssetHelper(AbstractAssetProvider assetProvider) + { + public Task GetAssetsAsync(AssetPath assetPath, HashSet checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + => assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken); + + public Task GetAssetsAsync(AssetPath assetPath, ChecksumCollection checksums, Action? callback, TArg? arg, CancellationToken cancellationToken) + => assetProvider.GetAssetsAsync(assetPath, checksums, callback, arg, cancellationToken); + } } internal static class AbstractAssetProviderExtensions From 8742f426868c612f095aaea001efb281db2ae790 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:09:54 -0700 Subject: [PATCH 0185/1047] Simplify types --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 06c57485f348a..4d484533e1850 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -43,7 +43,7 @@ public override async ValueTask GetAssetAsync( checksums.Add(checksum); using var _2 = ArrayBuilder.GetInstance(1, out var builder); - await this.GetAssetsAsync>( + await this.GetAssetHelper().GetAssetsAsync( assetPath, checksums, static (_, asset, builder) => builder.Add(asset), builder, cancellationToken).ConfigureAwait(false); From 99819ba019f46fc93769332f78d3d55bf9bc82d1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:10:48 -0700 Subject: [PATCH 0186/1047] Simplify types --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 2 +- .../Services/SourceGeneration/RemoteSourceGenerationService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index d65562f56b02c..ae6756e6fefaa 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -223,7 +223,7 @@ private async Task UpdateProjectsAsync( using var _5 = PooledHashSet.GetInstance(out var newChecksumsToSync); newChecksumsToSync.AddRange(newProjectIdToChecksum.Values); - await _assetProvider.GetAssetsAsync>( + await _assetProvider.GetAssetHelper().GetAssetsAsync( assetPath: AssetPathKind.ProjectStateChecksums, newChecksumsToSync, static (checksum, newProjectStateChecksum, newProjectIdToStateChecksums) => { diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 5a5a17d0021a4..6a974e5c64b6c 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -115,7 +115,7 @@ public async ValueTask HasGeneratorsAsync( // 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.GetAssetsAsync>( + await assetProvider.GetAssetHelper().GetAssetsAsync( projectId, checksums, static (_, analyzerReference, analyzerReferences) => analyzerReferences.Add(analyzerReference), From 45026384d6c71e4ca6d8bfb406d4630f6ee0d8b0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:18:02 -0700 Subject: [PATCH 0187/1047] Remove garbage --- .../InternalUtilities/EnumerableExtensions.cs | 12 ++++++++++++ .../Solution/SolutionCompilationState_Checksum.cs | 7 ++++++- .../Portable/Workspace/Solution/StateChecksums.cs | 8 +++++--- 3 files changed, 23 insertions(+), 4 deletions(-) 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/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index af37ec21138f8..134642f20beed 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -6,6 +6,7 @@ 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; @@ -163,8 +164,12 @@ 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); - using var _ = ArrayBuilder.GetInstance(out var checksums); + // 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) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 161d55e65cc93..372b4d34a8072 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Resources; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; @@ -534,11 +535,12 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - using var _ = ArrayBuilder.GetInstance(references.Count, out var checksums); + var checksums = new Checksum[references.Count]; + var index = 0; foreach (var reference in references) - checksums.Add(tuple.serializer.CreateChecksum(reference, tuple.cancellationToken)); + checksums[index++] = tuple.serializer.CreateChecksum(reference, tuple.cancellationToken); - return new ChecksumCollection(checksums.ToImmutableAndClear()); + return new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(checksums)); }, (serializer, cancellationToken)); } From 85456591da15d202e8930b1ce577e497eeb136ea Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:19:06 -0700 Subject: [PATCH 0188/1047] Remove garbage --- .../Workspace/Solution/TextDocumentStates.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 713a76b23060b..be4f6772f7905 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -321,19 +322,21 @@ static async (state, _, cancellationToken) => public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); - using var _2 = ArrayBuilder.GetInstance(_map.Count, out var textChecksums); + var attributeChecksums = new Checksum[_map.Count]; + var textChecksums = new Checksum[_map.Count]; + var index = 0; foreach (var (_, state) in _map) { var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - attributeChecksums.Add(stateChecksums.Info); - textChecksums.Add(stateChecksums.Text); + attributeChecksums[index] = stateChecksums.Info; + textChecksums[index] = stateChecksums.Text; + index++; } return new( - new ChecksumCollection(attributeChecksums.ToImmutableAndClear()), - new ChecksumCollection(textChecksums.ToImmutableAndClear()), + new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(attributeChecksums)), + new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(textChecksums)), SelectAsArray(static s => s.Id)); } From 7850f214ea6737e81ed70c2c5415c722ff0344c6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:19:23 -0700 Subject: [PATCH 0189/1047] Docs --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 2bc5d7ac459ef..9d8a029f2ebc1 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -44,9 +44,9 @@ await this.GetAssetHelper().GetAssetsAsync( (@this: this, projectsTasks, cancellationToken), cancellationToken).ConfigureAwait(false); - // Fetch the projects in parallel. 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, From ed7a495709057f92766be600a07364ca0b72a40c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:20:38 -0700 Subject: [PATCH 0190/1047] Cleanup --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 9d8a029f2ebc1..22fdc549072c8 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -95,15 +95,16 @@ public async Task CreateProjectInfoAsync(ProjectStateChecksums proj async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - using var _ = ArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); + var documentInfos = new DocumentInfo[checksumsAndIds.Length]; + var index = 0; foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); - documentInfos.Add(await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false)); + documentInfos[index++] = await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false); } - return documentInfos.ToImmutableAndClear(); + return ImmutableCollectionsMarshal.AsImmutableArray(documentInfos); } } From 1b8970c882c32410d9236fe0f1ca0121fef0655d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:22:18 -0700 Subject: [PATCH 0191/1047] Parallel fetch --- .../Remote/Core/AbstractAssetProvider.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 22fdc549072c8..caf517bfd76b6 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -67,30 +67,30 @@ public async Task CreateProjectInfoAsync(ProjectStateChecksums proj var compilationOptions = attributes.FixUpCompilationOptions( await GetAssetAsync(new(AssetPathKind.ProjectCompilationOptions, projectId), projectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false)); - var parseOptions = await GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false); + var parseOptionsTask = GetAssetAsync(new(AssetPathKind.ProjectParseOptions, projectId), projectChecksums.ParseOptions, cancellationToken); - var projectReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectProjectReferences, projectId), projectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectMetadataReferences, projectId), projectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await this.GetAssetsArrayAsync(new(AssetPathKind.ProjectAnalyzerReferences, 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); // 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 documentInfos = await CreateDocumentInfosAsync(projectChecksums.Documents).ConfigureAwait(false); - var additionalDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AdditionalDocuments).ConfigureAwait(false); - var analyzerConfigDocumentInfos = await CreateDocumentInfosAsync(projectChecksums.AnalyzerConfigDocuments).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(DocumentChecksumsAndIds checksumsAndIds) From 2abb5455b12407bb4beafffb73f16ef6402a1469 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:24:03 -0700 Subject: [PATCH 0192/1047] Simplify --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index caf517bfd76b6..652ad566b191a 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -197,14 +197,14 @@ public static async Task> GetAssetsArrayAsync( using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); - using var _2 = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); + var builder = ImmutableArray.CreateBuilder(checksumSet.Count); - await assetProvider.GetAssetsAsync>( + await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, static (checksum, asset, builder) => builder.Add(asset), builder, cancellationToken).ConfigureAwait(false); - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } } From 8c79721497ba94e19af21b94304c3d73769fc3ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:25:30 -0700 Subject: [PATCH 0193/1047] simplify --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 4d484533e1850..7d68a08cc0e82 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -107,7 +107,7 @@ async ValueTask SynchronizeSolutionAssetsWorkerAsync() await Task.WhenAll(tasks).ConfigureAwait(false); using var _3 = ArrayBuilder.GetInstance(out var allProjectStateChecksums); - await this.GetAssetsAsync>( + await this.GetAssetHelper().GetAssetsAsync( AssetPathKind.ProjectStateChecksums, solutionStateChecksum.Projects.Checksums, static (_, asset, allProjectStateChecksums) => allProjectStateChecksums.Add(asset), From 2a83852ee021c7c0777113cce8da7977daaef560 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:27:45 -0700 Subject: [PATCH 0194/1047] simplify --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index ae6756e6fefaa..a0151654cc002 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -267,8 +267,7 @@ await _assetProvider.GetAssetsAsync( assetPath: AssetPathKind.ProjectCompilationOptions, projectItemChecksums, cancellationToken).ConfigureAwait(false); } - using var _2 = ArrayBuilder.GetInstance(out var projectInfos); - using var _3 = ArrayBuilder.GetInstance(out var projectStateChecksumsToAdd); + using var _2 = ArrayBuilder.GetInstance(out var projectStateChecksumsToAdd); // added project foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) @@ -281,6 +280,7 @@ await _assetProvider.GetAssetsAsync( // efficiently in bulk and in parallel. await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); + using var _3 = ArrayBuilder.GetInstance(out var projectInfos); foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) From a43348f5396f1b916555b0ce35fc3e7f0cddf47f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:28:49 -0700 Subject: [PATCH 0195/1047] simplify --- .../SourceGeneration/RemoteSourceGenerationService.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 6a974e5c64b6c..edea6958f1693 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -7,6 +7,7 @@ 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; @@ -37,15 +38,15 @@ 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, DateTime generationDateTime)>.GetInstance(documentStates.Ids.Count, out var result); - + var result = new (SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)[documentStates.Ids.Count]; + var index = 0; foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); - result.Add((state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); + result[index++] = (state.Identity, state.GetContentIdentity(), state.GenerationDateTime); } - return result.ToImmutableAndClear(); + return ImmutableCollectionsMarshal.AsImmutableArray(result); }, cancellationToken); } From 1260bead00951efd6b838c7aeb16c401d9710911 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:29:46 -0700 Subject: [PATCH 0196/1047] simplify --- .../SourceGeneration/RemoteSourceGenerationService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index edea6958f1693..6d7e72d98ef17 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -58,17 +58,17 @@ 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 string[documentIds.Length]; + var index = 0; foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); var state = documentStates.GetRequiredState(id); var text = await state.GetTextAsync(cancellationToken).ConfigureAwait(false); - result.Add(text.ToString()); + result[index++] = text.ToString(); } - return result.ToImmutableAndClear(); + return ImmutableCollectionsMarshal.AsImmutableArray(result); }, cancellationToken); } From b6b607c64b37e3171407c07f4af0a6e70a0a75f8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:30:44 -0700 Subject: [PATCH 0197/1047] Cleanup --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 7d68a08cc0e82..e754650d6939a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -5,15 +5,12 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; -using Microsoft.CodeAnalysis.Shared.TestHooks; -using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; From 3d211efd5d8ebfdb9d73e91ecd64686eb2ca65e8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:31:41 -0700 Subject: [PATCH 0198/1047] Cleanup --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 372b4d34a8072..2d37424a9e1fe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -7,12 +7,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Resources; 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; From 89daf34a6e2869a3d2b754c96222feb77180464d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:36:17 -0700 Subject: [PATCH 0199/1047] In progresS --- .../Workspace/Solution/ChecksumsAndIds.cs | 45 +++++++++++++++++++ .../SolutionCompilationState_Checksum.cs | 4 +- .../Workspace/Solution/StateChecksums.cs | 16 +++---- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs index 474b4bb7e2f1a..6abc8ecfda663 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs @@ -80,6 +80,51 @@ public bool MoveNext() } } +internal readonly struct ProjectChecksumsAndIds +{ + public readonly ChecksumCollection Checksums; + public readonly ImmutableArray Ids; + + public ProjectChecksumsAndIds(ChecksumCollection checksums, ImmutableArray ids) + { + Contract.ThrowIfTrue(ids.Length != checksums.Children.Length); + + Checksums = checksums; + Ids = ids; + } + + public int Length => Ids.Length; + public Checksum Checksum => Checksums.Checksum; + + public void WriteTo(ObjectWriter writer) + { + this.Checksums.WriteTo(writer); + writer.WriteArray(this.Ids, static (writer, p) => p.WriteTo(writer)); + } + + public static ProjectChecksumsAndIds ReadFrom(ObjectReader reader) + { + return new( + ChecksumCollection.ReadFrom(reader), + reader.ReadArray(static reader => ProjectId.ReadFrom(reader))); + } + + public Enumerator GetEnumerator() + => new(this); + + public struct Enumerator(ProjectChecksumsAndIds checksumsAndIds) + { + private readonly ProjectChecksumsAndIds _checksumsAndIds = checksumsAndIds; + private int _index = -1; + + public bool MoveNext() + => ++_index < _checksumsAndIds.Length; + + public (Checksum checksum, ProjectId id) Current + => (_checksumsAndIds.Checksums.Children[_index], _checksumsAndIds.Ids[_index]); + } +} + internal readonly struct DocumentChecksumsAndIds { public readonly Checksum Checksum; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs index 134642f20beed..82600b5d6b1da 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState_Checksum.cs @@ -126,7 +126,7 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo } ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; - ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (FrozenSourceGeneratedDocumentStates != null) @@ -135,7 +135,7 @@ public async Task GetChecksumAsync(ProjectId projectId, CancellationTo var identityChecksums = FrozenSourceGeneratedDocumentStates.SelectAsArray( static (s, arg) => arg.serializer.CreateChecksum(s.Identity, cancellationToken: arg.cancellationToken), (serializer, cancellationToken)); - frozenSourceGeneratedDocumentTexts = await FrozenSourceGeneratedDocumentStates.GetTextChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); + frozenSourceGeneratedDocumentTexts = await FrozenSourceGeneratedDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false); frozenSourceGeneratedDocumentIdentities = new ChecksumCollection(identityChecksums); frozenSourceGeneratedDocumentGenerationDateTimes = FrozenSourceGeneratedDocumentStates.SelectAsArray(d => d.GenerationDateTime); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2d37424a9e1fe..56b1d95918b17 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -22,7 +22,7 @@ public SolutionCompilationStateChecksums( Checksum solutionState, Checksum sourceGeneratorExecutionVersionMap, // These arrays are all the same length if present, and reference the same documents in the same order. - ChecksumsAndIds? frozenSourceGeneratedDocumentTexts, + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts, ChecksumCollection? frozenSourceGeneratedDocumentIdentities, ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes) { @@ -53,7 +53,7 @@ public SolutionCompilationStateChecksums( /// /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. /// - public ChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } + public DocumentChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } // note: intentionally not part of the identity contract of this type. @@ -65,7 +65,7 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.SolutionState); checksums.AddIfNotNullChecksum(this.SourceGeneratorExecutionVersionMap); this.FrozenSourceGeneratedDocumentIdentities?.AddAllTo(checksums); - this.FrozenSourceGeneratedDocumentTexts?.Checksums.AddAllTo(checksums); + this.FrozenSourceGeneratedDocumentTexts?.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -92,13 +92,13 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) var sourceGeneratorExecutionVersionMap = Checksum.ReadFrom(reader); var hasFrozenSourceGeneratedDocuments = reader.ReadBoolean(); - ChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; + DocumentChecksumsAndIds? frozenSourceGeneratedDocumentTexts = null; ChecksumCollection? frozenSourceGeneratedDocumentIdentities = null; ImmutableArray frozenSourceGeneratedDocumentGenerationDateTimes = default; if (hasFrozenSourceGeneratedDocuments) { - frozenSourceGeneratedDocumentTexts = ChecksumsAndIds.ReadFrom(reader); + frozenSourceGeneratedDocumentTexts = DocumentChecksumsAndIds.ReadFrom(reader); frozenSourceGeneratedDocumentIdentities = ChecksumCollection.ReadFrom(reader); frozenSourceGeneratedDocumentGenerationDateTimes = reader.ReadArray(r => new DateTime(r.ReadInt64())); } @@ -205,7 +205,7 @@ await ChecksumCollection.FindAsync( internal sealed class SolutionStateChecksums( ProjectId? projectConeId, Checksum attributes, - ChecksumsAndIds projects, + ProjectChecksumsAndIds projects, ChecksumCollection analyzerReferences) { private ProjectCone? _projectCone; @@ -220,7 +220,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 @@ -258,7 +258,7 @@ 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; From 64bcfbc0043c8215a3869d2fd1e2cdfd133cd119 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:37:32 -0700 Subject: [PATCH 0200/1047] In progresS --- .../Workspace/Solution/StateChecksums.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 56b1d95918b17..2b3138742a0e0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -22,18 +22,18 @@ 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? frozenSourceGeneratedDocumentTexts, + DocumentChecksumsAndIds? frozenSourceGeneratedDocuments, ChecksumCollection? frozenSourceGeneratedDocumentIdentities, 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. - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocumentTexts.HasValue); - Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocumentTexts?.Length); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities.HasValue == frozenSourceGeneratedDocuments.HasValue); + Contract.ThrowIfFalse(frozenSourceGeneratedDocumentIdentities?.Count == frozenSourceGeneratedDocuments?.Length); SolutionState = solutionState; SourceGeneratorExecutionVersionMap = sourceGeneratorExecutionVersionMap; - FrozenSourceGeneratedDocumentTexts = frozenSourceGeneratedDocumentTexts; + FrozenSourceGeneratedDocuments = frozenSourceGeneratedDocuments; FrozenSourceGeneratedDocumentIdentities = frozenSourceGeneratedDocumentIdentities; FrozenSourceGeneratedDocumentGenerationDateTimes = frozenSourceGeneratedDocumentGenerationDateTimes; @@ -43,7 +43,7 @@ public SolutionCompilationStateChecksums( SolutionState, SourceGeneratorExecutionVersionMap, FrozenSourceGeneratedDocumentIdentities?.Checksum ?? Checksum.Null, - frozenSourceGeneratedDocumentTexts?.Checksum ?? Checksum.Null); + frozenSourceGeneratedDocuments?.Checksum ?? Checksum.Null); } public Checksum Checksum { get; } @@ -53,7 +53,7 @@ public SolutionCompilationStateChecksums( /// /// Checksums of the SourceTexts of the frozen documents directly. Not checksums of their DocumentStates. /// - public DocumentChecksumsAndIds? FrozenSourceGeneratedDocumentTexts { get; } + public DocumentChecksumsAndIds? FrozenSourceGeneratedDocuments { get; } public ChecksumCollection? FrozenSourceGeneratedDocumentIdentities { get; } // note: intentionally not part of the identity contract of this type. @@ -65,7 +65,7 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.SolutionState); checksums.AddIfNotNullChecksum(this.SourceGeneratorExecutionVersionMap); this.FrozenSourceGeneratedDocumentIdentities?.AddAllTo(checksums); - this.FrozenSourceGeneratedDocumentTexts?.AddAllTo(checksums); + this.FrozenSourceGeneratedDocuments?.AddAllTo(checksums); } public void Serialize(ObjectWriter writer) @@ -79,7 +79,7 @@ public void Serialize(ObjectWriter writer) writer.WriteBoolean(this.FrozenSourceGeneratedDocumentIdentities.HasValue); if (FrozenSourceGeneratedDocumentIdentities.HasValue) { - this.FrozenSourceGeneratedDocumentTexts!.Value.WriteTo(writer); + this.FrozenSourceGeneratedDocuments!.Value.WriteTo(writer); this.FrozenSourceGeneratedDocumentIdentities.Value.WriteTo(writer); writer.WriteArray(this.FrozenSourceGeneratedDocumentGenerationDateTimes, static (w, d) => w.WriteInt64(d.Ticks)); } @@ -136,7 +136,7 @@ public async Task FindAsync( if (compilationState.FrozenSourceGeneratedDocumentStates != null) { Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentIdentities.HasValue); - Contract.ThrowIfFalse(FrozenSourceGeneratedDocumentTexts.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) @@ -154,7 +154,7 @@ await ChecksumCollection.FindAsync( if (documentId != null) { // If the caller is asking for a specific document, we can just look it up directly. - var index = FrozenSourceGeneratedDocumentTexts.Value.Ids.IndexOf(documentId); + var index = FrozenSourceGeneratedDocuments.Value.Ids.IndexOf(documentId); if (index >= 0) { var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value.Children[index]; @@ -173,7 +173,7 @@ await ChecksumCollection.FindAsync( var identityChecksum = FrozenSourceGeneratedDocumentIdentities.Value[0]; if (searchingChecksumsLeft.Remove(identityChecksum)) { - var id = FrozenSourceGeneratedDocumentTexts.Value.Ids[i]; + var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); result[identityChecksum] = state.Identity; } From bcc5362af66e60abea106561f7078942f7bed342 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:38:54 -0700 Subject: [PATCH 0201/1047] In progresS --- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index a0151654cc002..3f417f6d07f0c 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -88,18 +88,18 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca } if (newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.HasValue && - newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.HasValue && + newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.HasValue && !newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentGenerationDateTimes.IsDefault) { var newSolutionFrozenSourceGeneratedDocumentIdentities = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentIdentities.Value; - var newSolutionFrozenSourceGeneratedDocumentTexts = newSolutionCompilationChecksums.FrozenSourceGeneratedDocumentTexts.Value; - var count = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums.Count; + var newSolutionFrozenSourceGeneratedDocuments = newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value; + var count = newSolutionFrozenSourceGeneratedDocuments.Ids.Length; using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); for (var i = 0; i < count; i++) { - var frozenDocumentId = newSolutionFrozenSourceGeneratedDocumentTexts.Ids[i]; - var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocumentTexts.Checksums[i]; + var frozenDocumentId = newSolutionFrozenSourceGeneratedDocuments.Ids[i]; + var frozenDocumentTextChecksum = newSolutionFrozenSourceGeneratedDocuments.TextChecksums[i]; var frozenDocumentIdentity = newSolutionFrozenSourceGeneratedDocumentIdentities[i]; var identity = await _assetProvider.GetAssetAsync( From 831e391cf5722323eb7f1964e21f20a928a9cd66 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:44:29 -0700 Subject: [PATCH 0202/1047] simplify --- .../Test.Next/Remote/SerializationValidator.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index b8f4c75cf1aae..c7f9578ffe043 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -222,13 +222,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) { @@ -252,16 +245,17 @@ internal void SolutionCompilationStateEqual(SolutionCompilationStateChecksums so if (solutionObject1.FrozenSourceGeneratedDocumentIdentities.HasValue) AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentIdentities.Value, solutionObject2.FrozenSourceGeneratedDocumentIdentities!.Value); - Assert.Equal(solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue, solutionObject2.FrozenSourceGeneratedDocumentTexts.HasValue); - if (solutionObject1.FrozenSourceGeneratedDocumentTexts.HasValue) - AssertChecksumCollectionEqual(solutionObject1.FrozenSourceGeneratedDocumentTexts.Value, solutionObject2.FrozenSourceGeneratedDocumentTexts!.Value); + Assert.Equal(solutionObject1.FrozenSourceGeneratedDocuments.HasValue, solutionObject2.FrozenSourceGeneratedDocuments.HasValue); + if (solutionObject1.FrozenSourceGeneratedDocuments.HasValue) + 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)); From e2c3814ecfa4f647c9061daaff25a621ce9aaa3c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:45:10 -0700 Subject: [PATCH 0203/1047] remove unused --- .../Workspace/Solution/TextDocumentStates.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index be4f6772f7905..e15b39a1d1a67 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -305,21 +305,6 @@ static async (state, _, cancellationToken) => await state.GetChecksumAsync(cance return new(documentChecksums, SelectAsArray(static s => s.Id)); } - public async ValueTask> GetTextChecksumsAndIdsAsync(CancellationToken cancellationToken) - { - var documentTextChecksums = await SelectAsArrayAsync( - static async (state, _, cancellationToken) => - { - var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - return stateChecksums.Text; - }, - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); - - var documentChecksums = new ChecksumCollection(documentTextChecksums); - return new(documentChecksums, SelectAsArray(static s => s.Id)); - } - public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { var attributeChecksums = new Checksum[_map.Count]; From b130c8f8beea0f47b8f35f4c2bab2f9cfabb1321 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 11:48:14 -0700 Subject: [PATCH 0204/1047] Separate types --- .../Workspace/Solution/ChecksumsAndIds.cs | 80 +++---------------- ...eneratedFileReplacingCompilationTracker.cs | 2 +- .../Workspace/Solution/TextDocumentStates.cs | 11 --- 3 files changed, 10 insertions(+), 83 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs index 6abc8ecfda663..23b49cca4c0dc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumsAndIds.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 Microsoft.CodeAnalysis.Shared.Extensions; @@ -11,75 +10,9 @@ 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 -{ - public readonly ChecksumCollection Checksums; - 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) - { - Contract.ThrowIfTrue(ids.Length != checksums.Children.Length); - - Checksums = checksums; - Ids = ids; - } - - public int Length => Ids.Length; - public Checksum Checksum => Checksums.Checksum; - - public void WriteTo(ObjectWriter writer) - { - this.Checksums.WriteTo(writer); - writer.WriteArray(this.Ids, s_writeTo); - } - - public static ChecksumsAndIds ReadFrom(ObjectReader reader) - { - return new( - ChecksumCollection.ReadFrom(reader), - reader.ReadArray(s_readId)); - } - - public Enumerator GetEnumerator() - => new(this); - - public struct Enumerator(ChecksumsAndIds checksumsAndIds) - { - private readonly ChecksumsAndIds _checksumsAndIds = checksumsAndIds; - private int _index = -1; - - public bool MoveNext() - => ++_index < _checksumsAndIds.Length; - - public (Checksum checksum, TId id) Current - => (_checksumsAndIds.Checksums.Children[_index], _checksumsAndIds.Ids[_index]); - } -} - internal readonly struct ProjectChecksumsAndIds { public readonly ChecksumCollection Checksums; @@ -120,11 +53,16 @@ public struct Enumerator(ProjectChecksumsAndIds checksumsAndIds) public bool MoveNext() => ++_index < _checksumsAndIds.Length; - public (Checksum checksum, ProjectId 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; @@ -178,7 +116,7 @@ public struct Enumerator(DocumentChecksumsAndIds checksumsAndIds) public bool MoveNext() => ++_index < _checksumsAndIds.Length; - public (Checksum attributeChecksum, Checksum textChecksum, DocumentId id) Current + 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/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 6f225f909ac20..990d4467db2a7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -145,7 +145,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); + (await _replacementDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index e15b39a1d1a67..09e672892f944 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -294,17 +294,6 @@ public int Compare(DocumentId? x, DocumentId? y) } } - public async ValueTask> GetChecksumsAndIdsAsync(CancellationToken cancellationToken) - { - var documentTextChecksums = await SelectAsArrayAsync( - static async (state, _, cancellationToken) => await state.GetChecksumAsync(cancellationToken).ConfigureAwait(false), - arg: default(VoidResult), - cancellationToken).ConfigureAwait(false); - - var documentChecksums = new ChecksumCollection(documentTextChecksums); - return new(documentChecksums, SelectAsArray(static s => s.Id)); - } - public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { var attributeChecksums = new Checksum[_map.Count]; From 58206dd1eee1147733c30e0c2ab60af619529308 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 13:44:12 -0700 Subject: [PATCH 0205/1047] Simplify more --- .../Workspace/Solution/TextDocumentStates.cs | 44 +++++-------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 09e672892f944..21e522eb393c7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -111,42 +111,18 @@ 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 TValue[_map.Count]; + var index = 0; foreach (var (_, state) in _map) - { - builder.Add(await selector(state, arg, cancellationToken).ConfigureAwait(true)); - } + result[index++] = selector(state, arg); - return builder.MoveToImmutable(); + return ImmutableCollectionsMarshal.AsImmutableArray(result); } public TextDocumentStates AddRange(ImmutableArray states) @@ -298,20 +274,22 @@ public async ValueTask GetDocumentChecksumsAndIdsAsync( { var attributeChecksums = new Checksum[_map.Count]; var textChecksums = new Checksum[_map.Count]; + var documentIds = new DocumentId[_map.Count]; var index = 0; - foreach (var (_, state) in _map) + foreach (var (documentId, state) in _map) { var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); attributeChecksums[index] = stateChecksums.Info; textChecksums[index] = stateChecksums.Text; + documentIds[index] = documentId; index++; } return new( new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(attributeChecksums)), new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(textChecksums)), - SelectAsArray(static s => s.Id)); + ImmutableCollectionsMarshal.AsImmutableArray(documentIds)); } public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) From 6eb6457982510d6ef0b0f062289df2eb335822ff Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 13:49:25 -0700 Subject: [PATCH 0206/1047] Update src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 3f417f6d07f0c..ca3315194f902 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -280,7 +280,7 @@ await _assetProvider.GetAssetsAsync( // efficiently in bulk and in parallel. await _assetProvider.SynchronizeProjectAssetsAsync(projectStateChecksumsToAdd, cancellationToken).ConfigureAwait(false); - using var _3 = ArrayBuilder.GetInstance(out var projectInfos); + using var _3 = ArrayBuilder.GetInstance(projectStateChecksumsToAdd.Count, out var projectInfos); foreach (var (projectId, newProjectChecksums) in newProjectIdToStateChecksums) { if (!oldProjectIdToStateChecksums.ContainsKey(projectId)) From 6840639e177de5eba5c718545eb1af84b5ee77b1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:08:46 -0700 Subject: [PATCH 0207/1047] Add builder --- .../Core/CompilerExtensions.projitems | 1 + .../Core/Utilities/FixedSizeArrayBuilder.cs | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index bbe77051ecf65..ac76082e5e8e3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -497,6 +497,7 @@ + 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..4b61967d943a3 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.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; +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +internal sealed class FixedSizeArrayBuilder +{ + private static readonly ObjectPool> s_pool = new(() => new()); + + private T[] _values = Array.Empty(); + private int _index; + + private FixedSizeArrayBuilder() + { + } + + public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSizeArrayBuilder builder) + { + builder = s_pool.Allocate(); + Contract.ThrowIfTrue(builder._values != Array.Empty()); + Contract.ThrowIfTrue(builder._index != 0); + builder._values = new T[capacity]; + + return new(builder); + } + + public void Add(T value) + => _values[_index++] = value; + + public T this[int index] + { + get => _values[index]; + set => _values[index] = value; + } + + public ImmutableArray MoveToImmutable() + { + Contract.ThrowIfTrue(_index != _values.Length); + var result = ImmutableCollectionsMarshal.AsImmutableArray(_values); + _values = Array.Empty(); + _index = 0; + return result; + } + + public struct PooledFixedSizeArrayBuilder(FixedSizeArrayBuilder builder) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + Contract.ThrowIfTrue(_disposed); + _disposed = true; + + // Put the builder back in the pool. If we were in the middle of creating hte array, but never moved it + // out, this will leak the array (can happen during things like cancellation). That's acceptable as that + // should not be the mainline path. And, in that event, it's not like we can use that array anyways as it + // won't be the right size for the next caller that needs a FixedSizeArrayBuilder of a different size. + builder._values = Array.Empty(); + builder._index = 0; + s_pool.Free(builder); + } + } +} From 37f328154051a9eecb6a870095d0d8f91c34d42b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:13:23 -0700 Subject: [PATCH 0208/1047] Update code --- .../Workspace/Solution/StateChecksums.cs | 7 +++-- .../Workspace/Solution/TextDocumentStates.cs | 27 +++++++++---------- .../Remote/Core/AbstractAssetProvider.cs | 7 +++-- .../RemoteSourceGenerationService.cs | 14 +++++----- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2b3138742a0e0..cfba1aa4da307 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -533,12 +533,11 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - var checksums = new Checksum[references.Count]; - var index = 0; + using var _ = FixedSizeArrayBuilder.GetInstance(references.Count, out var checksums); foreach (var reference in references) - checksums[index++] = tuple.serializer.CreateChecksum(reference, tuple.cancellationToken); + checksums.Add(tuple.serializer.CreateChecksum(reference, tuple.cancellationToken)); - return new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(checksums)); + return new ChecksumCollection(checksums.MoveToImmutable()); }, (serializer, cancellationToken)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 21e522eb393c7..392c914c438f0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -117,12 +117,11 @@ public ImmutableArray SelectAsArray(Func selecto public ImmutableArray SelectAsArray(Func selector, TArg arg) { - var result = new TValue[_map.Count]; - var index = 0; + using var _ = FixedSizeArrayBuilder.GetInstance(_map.Count, out var result); foreach (var (_, state) in _map) - result[index++] = selector(state, arg); + result.Add(selector(state, arg)); - return ImmutableCollectionsMarshal.AsImmutableArray(result); + return result.MoveToImmutable(); } public TextDocumentStates AddRange(ImmutableArray states) @@ -272,24 +271,22 @@ public int Compare(DocumentId? x, DocumentId? y) public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - var attributeChecksums = new Checksum[_map.Count]; - var textChecksums = new Checksum[_map.Count]; - var documentIds = new DocumentId[_map.Count]; + using var _1 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); + using var _2 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var textChecksums); + using var _3 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var documentIds); - var index = 0; foreach (var (documentId, state) in _map) { var stateChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - attributeChecksums[index] = stateChecksums.Info; - textChecksums[index] = stateChecksums.Text; - documentIds[index] = documentId; - index++; + attributeChecksums.Add(stateChecksums.Info); + textChecksums.Add(stateChecksums.Text); + documentIds.Add(documentId); } return new( - new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(attributeChecksums)), - new ChecksumCollection(ImmutableCollectionsMarshal.AsImmutableArray(textChecksums)), - ImmutableCollectionsMarshal.AsImmutableArray(documentIds)); + new ChecksumCollection(attributeChecksums.MoveToImmutable()), + new ChecksumCollection(textChecksums.MoveToImmutable()), + documentIds.MoveToImmutable()); } public void AddDocumentIdsWithFilePath(ref TemporaryArray temporaryArray, string filePath) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 652ad566b191a..f2c0ba6458849 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -95,16 +95,15 @@ await analyzerConfigDocumentInfosTask.ConfigureAwait(false), async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - var documentInfos = new DocumentInfo[checksumsAndIds.Length]; + using var _ = FixedSizeArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); - var index = 0; foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { cancellationToken.ThrowIfCancellationRequested(); - documentInfos[index++] = await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false); + documentInfos.Add(await CreateDocumentInfoAsync(documentId, attributeChecksum, textChecksum, cancellationToken).ConfigureAwait(false)); } - return ImmutableCollectionsMarshal.AsImmutableArray(documentInfos); + return documentInfos.MoveToImmutable(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 6d7e72d98ef17..1d2fe14ba42b6 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -38,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); - var result = new (SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)[documentStates.Ids.Count]; - var index = 0; + using var _ = FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>.GetInstance(documentStates.Ids.Count, out var result); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); - result[index++] = (state.Identity, state.GetContentIdentity(), state.GenerationDateTime); + result.Add((state.Identity, state.GetContentIdentity(), state.GenerationDateTime)); } - return ImmutableCollectionsMarshal.AsImmutableArray(result); + return result.MoveToImmutable(); }, cancellationToken); } @@ -58,17 +57,16 @@ public ValueTask> GetContentsAsync( var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - var result = new string[documentIds.Length]; - var index = 0; + using var _ = FixedSizeArrayBuilder.GetInstance(documentIds.Length, out var result); foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); var state = documentStates.GetRequiredState(id); var text = await state.GetTextAsync(cancellationToken).ConfigureAwait(false); - result[index++] = text.ToString(); + result.Add(text.ToString()); } - return ImmutableCollectionsMarshal.AsImmutableArray(result); + return result.MoveToImmutable(); }, cancellationToken); } From 2a1a500a61f61aee71e59747da48dc3b233b2262 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:15:12 -0700 Subject: [PATCH 0209/1047] More cases --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index f2c0ba6458849..bff20c2b845ac 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -196,7 +196,7 @@ public static async Task> GetAssetsArrayAsync( using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); - var builder = ImmutableArray.CreateBuilder(checksumSet.Count); + using var _ = FixedSizeArrayBuilder.GetInstance(checksumSet.Count, out var builder); await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, From b9d7595acdbfa40dc56c5acf5b41003a9c4031a4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:18:21 -0700 Subject: [PATCH 0210/1047] Docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 4b61967d943a3..0aba4afbaa7d4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -8,6 +8,13 @@ using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; +/// +/// A bare-bones, pooled 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. +/// internal sealed class FixedSizeArrayBuilder { private static readonly ObjectPool> s_pool = new(() => new()); From 704f683917982e5d1c0b27f15ed07e1dbdd6ced3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:19:35 -0700 Subject: [PATCH 0211/1047] Remove indexer --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 0aba4afbaa7d4..22f795d4b8507 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -15,6 +15,11 @@ /// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary /// array created to store the results. /// +/// +/// 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. +/// internal sealed class FixedSizeArrayBuilder { private static readonly ObjectPool> s_pool = new(() => new()); @@ -39,12 +44,6 @@ public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSiz public void Add(T value) => _values[_index++] = value; - public T this[int index] - { - get => _values[index]; - set => _values[index] = value; - } - public ImmutableArray MoveToImmutable() { Contract.ThrowIfTrue(_index != _values.Length); From 463a4c37a618d5da03a760c15a7432df036706cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:21:12 -0700 Subject: [PATCH 0212/1047] Docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 22f795d4b8507..071db1b435a3d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -15,11 +15,6 @@ /// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary /// array created to store the results. /// -/// -/// 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. -/// internal sealed class FixedSizeArrayBuilder { private static readonly ObjectPool> s_pool = new(() => new()); @@ -44,6 +39,13 @@ public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSiz public void Add(T value) => _values[_index++] = value; + /// + /// 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() { Contract.ThrowIfTrue(_index != _values.Length); From aa57c848843042beff5aa2b932c580ca1248b7ae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:22:10 -0700 Subject: [PATCH 0213/1047] Docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 071db1b435a3d..a1db353fbc08f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -26,6 +26,11 @@ private FixedSizeArrayBuilder() { } + /// + /// Gets a builder which wraps an array whose length is exactly . This array can only be + /// moved into a through , and only when exactly that + /// number of elements have been added. + /// public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSizeArrayBuilder builder) { builder = s_pool.Allocate(); From a30736aaff8d258411524de0a594c66f6d899545 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:31:29 -0700 Subject: [PATCH 0214/1047] Switch to non-copyable struct --- .../Workspace/Solution/StateChecksums.cs | 2 +- .../Workspace/Solution/TextDocumentStates.cs | 8 ++-- .../Remote/Core/AbstractAssetProvider.cs | 6 +-- .../RemoteSourceGenerationService.cs | 4 +- .../Core/Utilities/FixedSizeArrayBuilder.cs | 45 ++----------------- 5 files changed, 13 insertions(+), 52 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index cfba1aa4da307..7762b7c92f61f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -533,7 +533,7 @@ public static ChecksumCollection GetOrCreateChecksumCollection( references, static (references, tuple) => { - using var _ = FixedSizeArrayBuilder.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)); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 392c914c438f0..d67a3608d5ebe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -117,7 +117,7 @@ public ImmutableArray SelectAsArray(Func selecto public ImmutableArray SelectAsArray(Func selector, TArg arg) { - using var _ = FixedSizeArrayBuilder.GetInstance(_map.Count, out var result); + var result = new FixedSizeArrayBuilder(_map.Count); foreach (var (_, state) in _map) result.Add(selector(state, arg)); @@ -271,9 +271,9 @@ public int Compare(DocumentId? x, DocumentId? y) public async ValueTask GetDocumentChecksumsAndIdsAsync(CancellationToken cancellationToken) { - using var _1 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var attributeChecksums); - using var _2 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var textChecksums); - using var _3 = FixedSizeArrayBuilder.GetInstance(_map.Count, out var documentIds); + var attributeChecksums = new FixedSizeArrayBuilder(_map.Count); + var textChecksums = new FixedSizeArrayBuilder(_map.Count); + var documentIds = new FixedSizeArrayBuilder(_map.Count); foreach (var (documentId, state) in _map) { diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index bff20c2b845ac..fbbd5ec9a74ca 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -95,7 +95,7 @@ await analyzerConfigDocumentInfosTask.ConfigureAwait(false), async Task> CreateDocumentInfosAsync(DocumentChecksumsAndIds checksumsAndIds) { - using var _ = FixedSizeArrayBuilder.GetInstance(checksumsAndIds.Length, out var documentInfos); + var documentInfos = new FixedSizeArrayBuilder(checksumsAndIds.Length); foreach (var (attributeChecksum, textChecksum, documentId) in checksumsAndIds) { @@ -196,7 +196,7 @@ public static async Task> GetAssetsArrayAsync( using var _1 = PooledHashSet.GetInstance(out var checksumSet); checksumSet.AddAll(checksums.Children); - using var _ = FixedSizeArrayBuilder.GetInstance(checksumSet.Count, out var builder); + using var _ = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, @@ -204,6 +204,6 @@ await assetProvider.GetAssetHelper().GetAssetsAsync( builder, cancellationToken).ConfigureAwait(false); - return builder.MoveToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 1d2fe14ba42b6..58e9194da495a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -38,7 +38,7 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>.GetInstance(documentStates.Ids.Count, out var result); + var result = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>(documentStates.Ids.Count); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); @@ -57,7 +57,7 @@ public ValueTask> GetContentsAsync( var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - using var _ = FixedSizeArrayBuilder.GetInstance(documentIds.Length, out var result); + var result = new FixedSizeArrayBuilder(documentIds.Length); foreach (var id in documentIds) { Contract.ThrowIfFalse(id.IsSourceGenerated); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index a1db353fbc08f..89e1109e4fe50 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -15,32 +15,12 @@ /// (usually encountered when a cancellation token interrupts getting the final array), this will leak the intermediary /// array created to store the results. /// -internal sealed class FixedSizeArrayBuilder +[NonCopyable] +internal struct FixedSizeArrayBuilder(int capacity) { - private static readonly ObjectPool> s_pool = new(() => new()); - - private T[] _values = Array.Empty(); + private T[] _values = new T[capacity]; private int _index; - private FixedSizeArrayBuilder() - { - } - - /// - /// Gets a builder which wraps an array whose length is exactly . This array can only be - /// moved into a through , and only when exactly that - /// number of elements have been added. - /// - public static PooledFixedSizeArrayBuilder GetInstance(int capacity, out FixedSizeArrayBuilder builder) - { - builder = s_pool.Allocate(); - Contract.ThrowIfTrue(builder._values != Array.Empty()); - Contract.ThrowIfTrue(builder._index != 0); - builder._values = new T[capacity]; - - return new(builder); - } - public void Add(T value) => _values[_index++] = value; @@ -59,23 +39,4 @@ public ImmutableArray MoveToImmutable() _index = 0; return result; } - - public struct PooledFixedSizeArrayBuilder(FixedSizeArrayBuilder builder) : IDisposable - { - private bool _disposed; - - public void Dispose() - { - Contract.ThrowIfTrue(_disposed); - _disposed = true; - - // Put the builder back in the pool. If we were in the middle of creating hte array, but never moved it - // out, this will leak the array (can happen during things like cancellation). That's acceptable as that - // should not be the mainline path. And, in that event, it's not like we can use that array anyways as it - // won't be the right size for the next caller that needs a FixedSizeArrayBuilder of a different size. - builder._values = Array.Empty(); - builder._index = 0; - s_pool.Free(builder); - } - } } From f89b813a22d106ceabfc853264369d979f81b18c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:32:50 -0700 Subject: [PATCH 0215/1047] Add capacity --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index fbbd5ec9a74ca..6a3c7c067f3ad 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -194,6 +194,9 @@ public static async Task> GetAssetsArrayAsync( this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { using var _1 = PooledHashSet.GetInstance(out var checksumSet); +#if NET + checksumSet.EnsureCapacity(checksums.Children.Length); +#endif checksumSet.AddAll(checksums.Children); using var _ = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); From becb0c5db46400fd24d40d34e1f742c8fe6eaaeb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:45:03 -0700 Subject: [PATCH 0216/1047] Simplify code --- .../CSharpInlineDeclarationCodeFixProvider.cs | 9 +-------- .../AddParameter/AbstractAddParameterCodeFixProvider.cs | 5 +++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs index 19967461ade4f..34ac23b0be67a 100644 --- a/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/InlineDeclaration/CSharpInlineDeclarationCodeFixProvider.cs @@ -70,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( diff --git a/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs index 117b30f2ccd8d..c07b6e6b0f1e9 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; @@ -281,7 +282,7 @@ ImmutableArray NestByOverload() 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(); } } From df92e115a6dcc55ed0e05831659a4ee9ebb5154d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:45:11 -0700 Subject: [PATCH 0217/1047] no need to realize array --- .../UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs index 343efc3858dab..564251e4178d1 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs @@ -205,7 +205,7 @@ private static ExpressionSyntax GenerateTupleDeclaration(ITypeSymbol typeSymbol, return TupleExpression( OpenParenToken.WithTrailingTrivia(), - SeparatedList(builder.ToImmutable(), separatorBuilder.ToImmutableAndFree()), + SeparatedList(builder, separatorBuilder), CloseParenToken) .WithTrailingTrivia(parensDesignation.GetTrailingTrivia()); } From 5875b5aa0194ab4ea4cdf276ea7de359aea02d57 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:46:46 -0700 Subject: [PATCH 0218/1047] Switch builder type --- .../AddParameter/AbstractAddParameterCodeFixProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs index c07b6e6b0f1e9..03be933e28046 100644 --- a/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs @@ -245,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. @@ -277,7 +277,7 @@ ImmutableArray NestByOverload() builder.Add(codeAction); } - return builder.ToImmutableAndClear(); + return builder.MoveToImmutable(); } ImmutableArray NestByCascading() @@ -322,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 @@ -344,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) From 608afd9b31453bf59c64ca1c739dd875390aba0f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:47:55 -0700 Subject: [PATCH 0219/1047] Switch builder type --- ...nCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs index c03dc33e08ad9..44d0ae9e5e5a5 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs @@ -158,14 +158,14 @@ public override async Task TryGetMergedFixAsync( 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(); } } } From 338faccb7ce9398503f69046149a37bc651de4a8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:49:30 -0700 Subject: [PATCH 0220/1047] Switch builder type --- .../SyncNamespace/AbstractChangeNamespaceService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 6a945ab96565f..faf1fd2c20aab 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -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) From a2351acc241772ef2acf54595047ce73b84dba3e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:50:35 -0700 Subject: [PATCH 0221/1047] Switch builder type --- .../ImportCompletionProvider/ImportCompletionItem.cs | 4 ++-- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index a1c2a4b673d50..a1ac57e12e9bc 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -97,7 +97,7 @@ 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)); @@ -107,7 +107,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, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 89e1109e4fe50..5d70d521cb614 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -24,6 +24,12 @@ internal struct FixedSizeArrayBuilder(int capacity) public void Add(T value) => _values[_index++] = value; + public void AddRange(ImmutableArray values) + { + foreach (var v in values) + Add(v); + } + /// /// 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 Date: Fri, 12 Apr 2024 14:51:17 -0700 Subject: [PATCH 0222/1047] Switch builder type --- .../ImportCompletionProvider/ImportCompletionItem.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index a1ac57e12e9bc..4dd4a776e71b7 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; @@ -219,12 +220,7 @@ 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()); + return item.WithProperties([.. itemProperties, KeyValuePairUtil.Create(AlwaysFullyQualifyKey, AlwaysFullyQualifyKey)]); } public static bool ShouldAlwaysFullyQualify(CompletionItem item) => item.TryGetProperty(AlwaysFullyQualifyKey, out var _); From 23d2beb29cc96eb4fb7a4a4b3e28d07176438a6c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:51:56 -0700 Subject: [PATCH 0223/1047] Switch builder type --- .../AbstractDocumentHighlightsService.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index f25de00436c7c..1671cca3ac7fd 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -263,13 +263,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) From c4f733b5c204f22f984c57103de91c9414fc3f52 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:54:26 -0700 Subject: [PATCH 0224/1047] Switch builder type --- .../ExtractMethod/MethodExtractor.Analyzer.cs | 5 ++--- .../Core/Utilities/FixedSizeArrayBuilder.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs index 6611b672c28e8..63bf051b43f10 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs @@ -440,11 +440,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 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 5d70d521cb614..cb5656ff1ccb3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.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.Runtime.InteropServices; using Microsoft.CodeAnalysis.PooledObjects; @@ -30,6 +31,18 @@ public void AddRange(ImmutableArray values) Add(v); } + public void AddRange(IEnumerable values) + { + foreach (var v in values) + Add(v); + } + + public readonly void Sort() + { + if (_index > 1) + Array.Sort(_values, 0, _index, Comparer.Default); + } + /// /// 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 Date: Fri, 12 Apr 2024 14:55:07 -0700 Subject: [PATCH 0225/1047] Simplify --- .../Core/Portable/ImplementInterface/ImplementHelpers.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs b/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs index 86e490d30d974..9296d3a52047c 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, From 9db264430f8c44f6693fe7a0ed0f7a59a2e60567 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:55:39 -0700 Subject: [PATCH 0226/1047] Switch builder type --- .../Portable/Structure/BlockStructureServiceWithProviders.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From c8c58dd4c695eeac20287e1e238708e68f48b1d8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:56:01 -0700 Subject: [PATCH 0227/1047] Switch builder type --- .../ChainedExpression/AbstractChainedExpressionWrapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs index e6e6743f9622e..04098dde6b97d 100644 --- a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs +++ b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs @@ -230,11 +230,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) From 49a00d1bb8d9847609497ece1d30fe6a7b2865ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:57:10 -0700 Subject: [PATCH 0228/1047] Switch builder type --- .../Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs index 2690a5fa2c68b..321e5cb7ee1f2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs @@ -395,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(); } } } From a5bb57140321cfe0bea2f6adae5addfc454b5dd5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:57:40 -0700 Subject: [PATCH 0229/1047] Switch builder type --- .../UnifiedSuggestions/UnifiedSuggestedActionsSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 75513230f0080..98a966365fa1c 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); From a45c91c509983b8792bf6c984e6a819ef90a74ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:58:03 -0700 Subject: [PATCH 0230/1047] Switch builder type --- .../UnifiedSuggestions/UnifiedSuggestedActionsSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 98a966365fa1c..1f9db6243e242 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -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( From 4db884811849288cc94072890b9c7b4d07ff5c0b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:58:26 -0700 Subject: [PATCH 0231/1047] Switch builder type --- .../UnifiedSuggestions/UnifiedSuggestedActionsSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 1f9db6243e242..52c24a238cc98 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -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); From 56399c2b12c47b349b06a2ec9cf53c9bec2c0b1f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 14:59:40 -0700 Subject: [PATCH 0232/1047] Switch builder type --- .../Protocol/Handler/InlayHint/InlayHintHandler.cs | 4 ++-- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs index de557ed9f5240..01e97664d40f5 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 FixedSizeArrayBuilder(hints.Length); for (var i = 0; i < hints.Length; i++) { var hint = hints[i]; @@ -91,7 +91,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) inlayHints.Add(inlayHint); } - return inlayHints.ToArray(); + return inlayHints.MoveToArray(); } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index cb5656ff1ccb3..c867424ee26de 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -58,4 +58,13 @@ public ImmutableArray MoveToImmutable() _index = 0; return result; } + + public T[] MoveToArray() + { + Contract.ThrowIfTrue(_index != _values.Length); + var result = _values; + _values = Array.Empty(); + _index = 0; + return result; + } } From 2b62e27eb55e8eabcc0e31821753fe4b64ea6d1f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:00:07 -0700 Subject: [PATCH 0233/1047] Switch builder type --- .../Handler/SemanticTokens/SemanticTokensHelpers.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index 694d7166f02c8..05f9a10e1f0e1 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 From ee1e68000ebe2f5a541e600ef6a78863ca97ca40 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:00:42 -0700 Subject: [PATCH 0234/1047] Switch builder type --- .../Handler/SemanticTokens/SemanticTokensHelpers.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index 05f9a10e1f0e1..7bac4f013336a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs @@ -98,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( From 34f78e3f8730f6b7c760c8294d610db48d20d5c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:02:19 -0700 Subject: [PATCH 0235/1047] Switch builder type --- .../Handler/SemanticTokens/SemanticTokensHelpers.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index 7bac4f013336a..b40ba1360aac7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs @@ -236,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; @@ -245,6 +243,7 @@ private static int[] ComputeTokens( var tokenTypeMap = SemanticTokensSchema.GetSchema(supportsVisualStudioExtensions).TokenTypeMap; + var data = new FixedSizeArrayBuilder(5 * classifiedSpans.Count); for (var currentClassifiedSpanIndex = 0; currentClassifiedSpanIndex < classifiedSpans.Count; currentClassifiedSpanIndex++) { currentClassifiedSpanIndex = ComputeNextToken( @@ -253,10 +252,14 @@ 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(); + return data.MoveToArray(); } private static int ComputeNextToken( From 4edfaf4d872bc2f1c6fbe56fcfcdcc6ef150a01d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:02:40 -0700 Subject: [PATCH 0236/1047] Simplify --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index c867424ee26de..3ce71fa0c950c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -51,13 +51,7 @@ public readonly void Sort() /// The internal buffer will reset to an empty array, meaning no more items could ever be added to it. /// public ImmutableArray MoveToImmutable() - { - Contract.ThrowIfTrue(_index != _values.Length); - var result = ImmutableCollectionsMarshal.AsImmutableArray(_values); - _values = Array.Empty(); - _index = 0; - return result; - } + => ImmutableCollectionsMarshal.AsImmutableArray(MoveToArray()); public T[] MoveToArray() { From 96b332263570b8b72e9c6fea5e6e257dfcd3a27a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:03:39 -0700 Subject: [PATCH 0237/1047] Simplify --- .../Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 3f417f6d07f0c..b6389dc59598f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -95,7 +95,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca var newSolutionFrozenSourceGeneratedDocuments = newSolutionCompilationChecksums.FrozenSourceGeneratedDocuments.Value; var count = newSolutionFrozenSourceGeneratedDocuments.Ids.Length; - using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>.GetInstance(count, out var frozenDocuments); + var frozenDocuments = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity identity, DateTime generationDateTime, SourceText text)>(count); for (var i = 0; i < count; i++) { var frozenDocumentId = newSolutionFrozenSourceGeneratedDocuments.Ids[i]; @@ -113,7 +113,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca frozenDocuments.Add((identity, generationDateTime, text)); } - solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.ToImmutableAndClear()); + solution = solution.WithFrozenSourceGeneratedDocuments(frozenDocuments.MoveToImmutable()); } if (oldSolutionCompilationChecksums.SourceGeneratorExecutionVersionMap != From 5958326d6af21464a56984eed4b5cbbc4d6794b3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:04:17 -0700 Subject: [PATCH 0238/1047] Switch builder type --- .../RemoteSemanticClassificationService.Caching.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs index 1abec95dd1e99..f8fca15aa0a1b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs @@ -310,7 +310,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 +324,7 @@ private static ImmutableArray Read(ObjectReader reader) } } - return classifiedSpans.ToImmutableAndClear(); + return classifiedSpans.MoveToImmutable(); } catch { From 761663dfea92a9bef38e42cea6306e63dcff40d1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:05:11 -0700 Subject: [PATCH 0239/1047] Switch builder type --- .../Classification/IRemoteSemanticClassificationService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From 7e04145c568030a42919e010d0e2b6c6b3150a2e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:08:10 -0700 Subject: [PATCH 0240/1047] Switch builder type --- src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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(); } } From d4dbced2a7b45124b4fd01af6398d62447dec69b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:08:27 -0700 Subject: [PATCH 0241/1047] Switch builder type --- .../Portable/Shared/Extensions/IParameterSymbolExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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(); } } From e8ff1a6092194cd7ec64267934251cd4633560b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:10:03 -0700 Subject: [PATCH 0242/1047] Switch builder type --- .../Compiler/Core/Extensions/ImmutableArrayExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) From 77092820c643a02e918d401537fde0cf30a9374f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:10:19 -0700 Subject: [PATCH 0243/1047] Switch builder type --- .../Compiler/Core/Extensions/ObjectWriterExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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(); } } From c55b63e8c7159dd567fdd7b39905c3bb170e7910 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:10:55 -0700 Subject: [PATCH 0244/1047] Switch builder type --- .../Compiler/Core/Utilities/SpecializedTasks.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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(); } /// From 977bcebce9318d5974b7950b39315c69a2ae18cb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:13:13 -0700 Subject: [PATCH 0245/1047] Switch builder type --- .../DocumentOutline/DocumentOutlineViewModel_Utilities.cs | 4 ++-- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 5 ++++- .../Compiler/Core/Utilities/SymbolEquivalenceComparer.cs | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs index 789dc0c6c2760..b96a3ef86dfcc 100644 --- a/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs +++ b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs @@ -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( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 3ce71fa0c950c..71bb9f7d571f0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -38,9 +38,12 @@ public void AddRange(IEnumerable values) } public readonly void Sort() + => Sort(Comparer.Default); + + public readonly void Sort(IComparer comparer) { if (_index > 1) - Array.Sort(_values, 0, _index, Comparer.Default); + Array.Sort(_values, 0, _index, comparer); } /// 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); From e0a62fd6e514b52f47d9c122ebfaa517f180c5cf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:19:58 -0700 Subject: [PATCH 0246/1047] Docs --- .../Core/Utilities/FixedSizeArrayBuilder.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 71bb9f7d571f0..e7630d4369cc3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -16,6 +16,26 @@ /// (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 used when all of the following is 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. If the capacity is just intended as +/// a rough hint then should be used instead. +/// +/// +/// Exactly that number of elements is actually added prior to calling . This means no +/// patterns like "AddIfNotNull". If the exact number of calls is not guaranteed then should be used instead. +/// +/// +/// The builder will be moved to an array (see ) or (see ). If the builder is intended just for a scratch buffer, then should be used instead. +/// +/// +/// [NonCopyable] internal struct FixedSizeArrayBuilder(int capacity) { From 85cbdedec33dc7661b667c8a6df3b29ab5c61ad4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:23:24 -0700 Subject: [PATCH 0247/1047] docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index e7630d4369cc3..b7eb2ae2aeeca 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -10,11 +10,11 @@ using Roslyn.Utilities; /// -/// A bare-bones, pooled 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. +/// 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 used when all of the following is true: From 1b76e1fa9de2555d3730578cc093405060437506 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 15:24:22 -0700 Subject: [PATCH 0248/1047] docs --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index b7eb2ae2aeeca..898ed535a4833 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -21,20 +21,20 @@ /// /// /// 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. If the capacity is just intended as -/// a rough hint then should be used instead. +/// 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". If the exact number of calls is not guaranteed then should be used instead. +/// patterns like "AddIfNotNull". /// /// /// The builder will be moved to an array (see ) or (see ). If the builder is intended just for a scratch buffer, then should be used instead. +/// cref="MoveToImmutable"/>). /// /// +/// If any of the above are not true. For example, the capacity is a rought 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) From e2e8fdbe9d16d9cc48ec854fb6cd84b435ffc0d4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:04:43 -0700 Subject: [PATCH 0249/1047] Fix ambiguity --- .../Providers/ImportCompletionProvider/ImportCompletionItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index 4dd4a776e71b7..8222f4779903c 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -220,7 +220,8 @@ private static (ISymbol? symbol, int overloadCount) GetSymbolAndOverloadCount(Co public static CompletionItem MarkItemToAlwaysFullyQualify(CompletionItem item) { var itemProperties = item.GetProperties(); - return item.WithProperties([.. itemProperties, KeyValuePairUtil.Create(AlwaysFullyQualifyKey, AlwaysFullyQualifyKey)]); + ImmutableArray> properties = [.. itemProperties, KeyValuePairUtil.Create(AlwaysFullyQualifyKey, AlwaysFullyQualifyKey)]; + return item.WithProperties(properties); } public static bool ShouldAlwaysFullyQualify(CompletionItem item) => item.TryGetProperty(AlwaysFullyQualifyKey, out var _); From 68ec52d99465ea80f71b4e5b4f9135a6e86f7df5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:05:45 -0700 Subject: [PATCH 0250/1047] Add check before adding --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 898ed535a4833..54455a0a020f6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -47,6 +47,7 @@ public void Add(T value) public void AddRange(ImmutableArray values) { + Contract.ThrowIfTrue(_index + values.Length <= _values.Length) foreach (var v in values) Add(v); } From b76289b830ef9a6e76d3513af667d4834367ea6f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:06:33 -0700 Subject: [PATCH 0251/1047] clarify --- .../Services/SourceGeneration/RemoteSourceGenerationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 58e9194da495a..fa6ca45e97d70 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -38,7 +38,7 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr var project = solution.GetRequiredProject(projectId); var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); - var result = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>(documentStates.Ids.Count); + var result = new FixedSizeArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity, DateTime generationDateTime)>(documentStates.States.Count); foreach (var (id, state) in documentStates.States) { Contract.ThrowIfFalse(id.IsSourceGenerated); From 55a16c40cd9021f885eed3db52457f073fb084a0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:07:50 -0700 Subject: [PATCH 0252/1047] grammar --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 54455a0a020f6..8c94c0a97e171 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -17,7 +17,7 @@ /// created to store the results. /// /// -/// This type should only used when all of the following is true: +/// 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 From 4b12ae86aceb09b9c06001bed68071df9b87f177 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:08:29 -0700 Subject: [PATCH 0253/1047] grammar --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 8c94c0a97e171..fb9506b90baf8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -32,9 +32,9 @@ /// cref="MoveToImmutable"/>). /// /// -/// If any of the above are not true. For example, the capacity is a rought 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. +/// 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) From 818edfb10dc5ee8b40dbc7761585410676fed306 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:09:25 -0700 Subject: [PATCH 0254/1047] use array --- .../Protocol/Handler/InlayHint/InlayHintHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs index 01e97664d40f5..a940eea0b8c77 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs @@ -57,7 +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 FixedSizeArrayBuilder(hints.Length); + 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[0] = inlayHint; } - return inlayHints.MoveToArray(); + return inlayHints; } /// From f74c9f465ce4fb21de87ef23d75153e5362827db Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:12:28 -0700 Subject: [PATCH 0255/1047] fix --- .../Protocol/Handler/InlayHint/InlayHintHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs index a940eea0b8c77..8da7e981f854a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/InlayHint/InlayHintHandler.cs @@ -88,7 +88,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(InlayHintParams request) Data = new InlayHintResolveData(resultId, i, request.TextDocument) }; - inlayHints[0] = inlayHint; + inlayHints[i] = inlayHint; } return inlayHints; From 43ec1cdd94380f524d5eb744dda53538daafac6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:25:28 -0700 Subject: [PATCH 0256/1047] fix --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index fb9506b90baf8..d1b7d88ffd632 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -47,7 +47,7 @@ public void Add(T value) public void AddRange(ImmutableArray values) { - Contract.ThrowIfTrue(_index + values.Length <= _values.Length) + Contract.ThrowIfTrue(_index + values.Length <= _values.Length); foreach (var v in values) Add(v); } From b1e7f9b79a20295e29b3e6888b2d5e605d21f7a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:29:09 -0700 Subject: [PATCH 0257/1047] optimize array addition --- .../Core/Utilities/FixedSizeArrayBuilder.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index d1b7d88ffd632..72e5ccee43894 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -45,13 +45,38 @@ internal struct FixedSizeArrayBuilder(int capacity) 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) From 07b71232f61398051a4ba5ef3b022f13a0687486 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:35:51 -0700 Subject: [PATCH 0258/1047] Invert --- .../Compiler/Core/Utilities/FixedSizeArrayBuilder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs index 72e5ccee43894..1a0ade6ecf363 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/FixedSizeArrayBuilder.cs @@ -49,28 +49,28 @@ public void Add(T value) public void AddRange(ImmutableArray values) { - Contract.ThrowIfTrue(_index + values.Length <= _values.Length); + 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); + 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); + 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); + Contract.ThrowIfTrue(_index + values.Count > _values.Length); foreach (var v in values) Add(v); } From 42f3f868108c29a7cde5373870f33e41de645534 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:37:53 -0700 Subject: [PATCH 0259/1047] use helper that supports inference --- .../CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) From 3b1ffbca51bef92cd9588aeedfc76ecfc3613366 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:38:08 -0700 Subject: [PATCH 0260/1047] use helper that supports inference --- .../Core.Wpf/Interactive/InteractiveWindowResetCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs index f83ca947cb379..db725791fe4e8 100644 --- a/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs +++ b/src/EditorFeatures/Core.Wpf/Interactive/InteractiveWindowResetCommand.cs @@ -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); } } From 07afeda0b3139631a344e7665455bddaa1f5af6e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 13 Apr 2024 09:38:17 +1000 Subject: [PATCH 0261/1047] Remove unused using --- .../AbstractLanguageServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs index 7ee133ef92232..f140ff52b78ee 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Threading; From 8ab455a250352e570b5b27cba802863da69fd19e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:38:22 -0700 Subject: [PATCH 0262/1047] use helper that supports inference --- .../Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index 9265be8baa8f4..8c93a15e83032 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -391,7 +391,7 @@ private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference) } OnTagsChangedForBuffer( - [new KeyValuePair(buffer, difference)], + [KeyValuePairUtil.Create(buffer, difference)], highPriority: false); } } From 222103e7db2555d5d773be1622b1a1f57ea2be7a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:38:38 -0700 Subject: [PATCH 0263/1047] use helper that supports inference --- .../CSharpTest/ExtractMethod/ExtractMethodBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); } From b99c8df9ae94d494f0ccbbaac288619abf6a85ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:38:54 -0700 Subject: [PATCH 0264/1047] use helper that supports inference --- .../Diagnostics/TestAnalyzerReferenceByLanguage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From fac587d153899ca5a296956706df823ba338917f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:39:02 -0700 Subject: [PATCH 0265/1047] use helper that supports inference --- .../Core/Portable/CodeRefactorings/CodeRefactoringService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 77cffe24feeb2..d0326a24686fe 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)); From a23d4969e384e207916f24ddaa1cc34b3c630850 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:39:12 -0700 Subject: [PATCH 0266/1047] use helper that supports inference --- src/Features/Core/Portable/Completion/CommonCompletionItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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( From e40f683f83d88c150538d5720227908029e93da3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:39:22 -0700 Subject: [PATCH 0267/1047] use helper that supports inference --- src/Features/Core/Portable/Completion/CompletionItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 7365fa0f801008299756e662a4838e3049bce3dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:39:48 -0700 Subject: [PATCH 0268/1047] use helper that supports inference --- .../Providers/AbstractAwaitCompletionProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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), From 300137c2d222cecbc7a8e6bf551366c46bf00c90 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:41:03 -0700 Subject: [PATCH 0269/1047] use helper that supports inference --- .../Providers/AbstractInternalsVisibleToCompletionProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); } From 6e89af2c9b1c606a61a98cc809e97f858e848172 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:41:16 -0700 Subject: [PATCH 0270/1047] use helper that supports inference --- .../ImportCompletionProvider/ImportCompletionItem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index 8222f4779903c..5d714b7a07c13 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -47,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(); @@ -100,7 +100,7 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att // Remember the full type name so we can get the symbol when description is displayed. 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); From 1a3419f1767d1a111654aacb1286eab59d5457d0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:41:41 -0700 Subject: [PATCH 0271/1047] use helper that supports inference --- .../Providers/MemberInsertingCompletionItem.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs index 026b44ed4591e..9b7d077d739f4 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; @@ -24,14 +25,14 @@ public static CompletionItem Create( CompletionItemRules rules) { var props = ImmutableArray.Create( - new KeyValuePair("Line", line.ToString()), - new KeyValuePair("Modifiers", modifiers.ToString()), - new KeyValuePair("TokenSpanEnd", token.Span.End.ToString())); + KeyValuePairUtil.Create("Line", line.ToString()), + KeyValuePairUtil.Create("Modifiers", modifiers.ToString()), + KeyValuePairUtil.Create("TokenSpanEnd", token.Span.End.ToString())); return SymbolCompletionItem.CreateWithSymbolId( displayText: displayText, displayTextSuffix: displayTextSuffix, - symbols: ImmutableArray.Create(symbol), + symbols: [symbol], contextPosition: descriptionPosition, properties: props, rules: rules, From 3a9860131ebd48ab4cefaf00504d6113e65ee4f4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:41:52 -0700 Subject: [PATCH 0272/1047] use helper that supports inference --- .../Completion/Providers/Snippets/SnippetCompletionItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs index 63e7315b70148..8feb6c554d1a7 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs @@ -24,8 +24,8 @@ public static CompletionItem Create( ImmutableArray additionalFilterTexts) { var props = ImmutableArray.Create( - new KeyValuePair("Position", position.ToString()), - new KeyValuePair(SnippetIdentifierKey, snippetIdentifier)); + KeyValuePairUtil.Create("Position", position.ToString()), + KeyValuePairUtil.Create(SnippetIdentifierKey, snippetIdentifier)); return CommonCompletionItem.Create( displayText: displayText, From 5923edb1cb18c851da06bc46ff281623d7a9b0d7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:42:08 -0700 Subject: [PATCH 0273/1047] use helper that supports inference --- .../Providers/SymbolCompletionItem.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index 0990ee2947f07..d14c95979fec8 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Tags; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers; @@ -50,10 +51,10 @@ 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); @@ -80,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) @@ -225,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)))); } } From 8066e217bf7bdae4dce04f89cfa4cdef25eb9007 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:42:22 -0700 Subject: [PATCH 0274/1047] use helper that supports inference --- .../Completion/Providers/XmlDocCommentCompletionItem.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs index 85c33ff03a87b..49a34ce5ba094 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; @@ -15,8 +16,8 @@ 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)); + KeyValuePairUtil.Create(BeforeCaretText, beforeCaretText), + KeyValuePairUtil.Create(AfterCaretText, afterCaretText)); // Set isComplexTextEdit to be always true for simplicity, even // though we don't always need to make change outside the default From 0a1aafb660f69f26ddad5356629bdf3674b2828d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:43:56 -0700 Subject: [PATCH 0275/1047] use collectione xpressions --- .../Providers/MemberInsertingCompletionItem.cs | 10 ++++------ .../Providers/Snippets/SnippetCompletionItem.cs | 8 +++----- .../Providers/XmlDocCommentCompletionItem.cs | 8 +++----- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs index 9b7d077d739f4..ad5d486533cd3 100644 --- a/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/MemberInsertingCompletionItem.cs @@ -24,17 +24,15 @@ public static CompletionItem Create( int descriptionPosition, CompletionItemRules rules) { - var props = ImmutableArray.Create( - KeyValuePairUtil.Create("Line", line.ToString()), - KeyValuePairUtil.Create("Modifiers", modifiers.ToString()), - KeyValuePairUtil.Create("TokenSpanEnd", token.Span.End.ToString())); - return SymbolCompletionItem.CreateWithSymbolId( displayText: displayText, displayTextSuffix: displayTextSuffix, 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/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs index 8feb6c554d1a7..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( - KeyValuePairUtil.Create("Position", position.ToString()), - KeyValuePairUtil.Create(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/XmlDocCommentCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs index 49a34ce5ba094..a5031ba84843a 100644 --- a/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/XmlDocCommentCompletionItem.cs @@ -15,10 +15,6 @@ internal static class XmlDocCommentCompletionItem public static CompletionItem Create(string displayText, string beforeCaretText, string afterCaretText, CompletionItemRules rules) { - var props = ImmutableArray.Create( - KeyValuePairUtil.Create(BeforeCaretText, beforeCaretText), - KeyValuePairUtil.Create(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. @@ -28,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); } From 259a6e909e3fa6adaf11e8690f326cf657789607 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:44:29 -0700 Subject: [PATCH 0276/1047] use helper that supports inference --- .../RegexEmbeddedCompletionProvider.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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) { From c985b70e461070157a1366bd84eba8aaef50ba76 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:45:03 -0700 Subject: [PATCH 0277/1047] use helper that supports inference --- .../Completion/CompletionProviders/CrefCompletionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From 21eb4832d6b9c30526ee299905aac38cec697cb9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:45:37 -0700 Subject: [PATCH 0278/1047] use helper that supports inference --- .../UnnamedSymbolCompletionProvider_Conversions.cs | 6 +++--- .../UnnamedSymbolCompletionProvider_Indexers.cs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) 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) { From 39490f62c2fba45608f37b86f47eaaec27dcbed9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:45:46 -0700 Subject: [PATCH 0279/1047] use helper that supports inference --- .../UnnamedSymbolCompletionProvider_Operators.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..2d5375d52e338 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. From 0eb584c2e879a6acf08505ef99af9e9499947813 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:45:57 -0700 Subject: [PATCH 0280/1047] use helper that supports inference --- .../UnnamedSymbolCompletionProvider_Operators.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2d5375d52e338..5c0c652ec2e75 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs @@ -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)); } From b94862bf8a701c3b74f66343e05386ec81c18442 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:46:07 -0700 Subject: [PATCH 0281/1047] use helper that supports inference --- .../CompletionProviders/PartialTypeCompletionProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) From 9383f2735e51338f999b11c7a808eb317456f744 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:46:42 -0700 Subject: [PATCH 0282/1047] use helper that supports inference --- .../Workspaces/TestWorkspace_XmlConsumption.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index ef18057196fcd..2ae90f43f3550 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -296,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); From 3eb53b26cb0bdc2dc9edd32e7ad9ebdbcfc88775 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:46:51 -0700 Subject: [PATCH 0283/1047] use helper that supports inference --- .../Core/Portable/Differencing/LongestCommonSubsequence.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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; From e148a18cccbee4f95900ffa66e014df0fb8fc861 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:46:59 -0700 Subject: [PATCH 0284/1047] use helper that supports inference --- .../FindReferences/Finders/AbstractReferenceFinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 7383f6e71739a..e8782e33842a3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -814,7 +814,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; } } From 2c43ab7182e1228b103e557a7a4d115023926165 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:47:15 -0700 Subject: [PATCH 0285/1047] use helper that supports inference --- src/Workspaces/Core/Portable/Log/AbstractLogAggregator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); From da6a4bf800a0d4fd70113c3e5d978a1a5aeea93c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:47:29 -0700 Subject: [PATCH 0286/1047] use helper that supports inference --- .../Portable/Shared/Extensions/ILanguageMetadataExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 03b518ced74a5105f7cfc93ca383bc894dcd436c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:47:40 -0700 Subject: [PATCH 0287/1047] use helper that supports inference --- .../Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs index 80fcf4203170b..69e3298087b6b 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(); } From 851182382cd02e6e1fff7ec5adc1b3f10d413367 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:47:50 -0700 Subject: [PATCH 0288/1047] use helper that supports inference --- .../Core/Portable/Workspace/Solution/SolutionState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 4d3f278d7554a..c23854f2e7d35 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -1173,7 +1173,7 @@ 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(); From 71fda75950ca9d8a2fa25f08c17a037db3c7c765 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 16:49:02 -0700 Subject: [PATCH 0289/1047] use helper that supports inference --- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 2591d975938a0..90a03a7481fc5 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -103,7 +103,7 @@ async Task>> GetAssetFromAssetServiceAsync(I foreach (var checksum in checksums) { - items.Add(new KeyValuePair(checksum, await assetService.GetAssetAsync( + items.Add(KeyValuePairUtil.Create(checksum, await assetService.GetAssetAsync( AssetPath.FullLookupForTesting, checksum, CancellationToken.None).ConfigureAwait(false))); } From a9536ffb95835b946dd113c4e9e5049d97a640d3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:28:06 -0700 Subject: [PATCH 0290/1047] fix --- .../Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs index b40ba1360aac7..d83ae66ec4f64 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs @@ -243,7 +243,7 @@ private static int[] ComputeTokens( var tokenTypeMap = SemanticTokensSchema.GetSchema(supportsVisualStudioExtensions).TokenTypeMap; - var data = new FixedSizeArrayBuilder(5 * classifiedSpans.Count); + using var _ = ArrayBuilder.GetInstance(5 * classifiedSpans.Count, out var data); for (var currentClassifiedSpanIndex = 0; currentClassifiedSpanIndex < classifiedSpans.Count; currentClassifiedSpanIndex++) { currentClassifiedSpanIndex = ComputeNextToken( @@ -259,7 +259,7 @@ private static int[] ComputeTokens( data.Add(tokenModifiers); } - return data.MoveToArray(); + return data.ToArray(); } private static int ComputeNextToken( From 683292d090089073a69c5ecfbafe347212ffc2c1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:32:48 -0700 Subject: [PATCH 0291/1047] Move to collection exprs --- .../IDEDiagnosticIdToOptionMappingHelper.cs | 2 +- ...moveUnnecessaryImportsDiagnosticAnalyzer.cs | 2 +- ...AutomaticLineEnderCommandHandler_Helpers.cs | 8 ++++---- .../UI/Dashboard/RenameDashboard.xaml.cs | 2 +- .../QuickInfo/ProjectionBufferContent.cs | 2 +- .../AbstractToggleBlockCommentBase.cs | 2 +- .../Core/Editor/TextEditApplication.cs | 2 +- .../Aggregator/SettingsAggregator.cs | 2 +- .../DataProvider/SettingsProviderBase.cs | 2 +- .../Core/InlineRename/InlineRenameSession.cs | 2 +- .../AsyncCompletion/CompletionSource.cs | 2 +- .../AlwaysActivateInProcLanguageClient.cs | 2 +- .../NavigationBar/NavigationBarController.cs | 4 ++-- .../Preview/AbstractPreviewFactoryService.cs | 4 ++-- .../Test/CodeFixes/ExtensionOrderingTests.cs | 4 ++-- .../Test/Emit/CompilationOutputsTests.cs | 2 +- .../Test/Structure/StructureTaggerTests.cs | 2 +- .../AsynchronousOperationListenerTests.cs | 2 +- .../Test/Workspaces/TextFactoryTests.cs | 5 ++++- .../NavigateTo/AbstractNavigateToTests.cs | 2 +- .../ExpressionCompiler/UsingDebugInfoTests.cs | 8 ++++---- .../ResultProvider/Helpers/ArrayBuilder.cs | 2 +- .../Core/Test/FunctionResolver/Process.cs | 2 +- .../Core/Test/FunctionResolver/Resolver.cs | 2 +- .../Test/ResultProvider/ReflectionUtilities.cs | 2 +- .../DeclarationNameCompletionProvider.cs | 2 +- .../CSharpConvertLinqQueryToForEachProvider.cs | 2 +- .../AbstractToMethodConverter.cs | 4 ++-- ...egularConstructorCodeRefactoringProvider.cs | 2 +- .../CSharpEditAndContinueAnalyzer.cs | 2 +- ...CSharpGenerateParameterizedMemberService.cs | 2 +- ...SharpDiagnosticAnalyzerQuickInfoProvider.cs | 12 ++++++------ .../Helpers/EditAndContinueValidation.cs | 2 +- .../EditAndContinue/TopLevelEditingTests.cs | 2 +- .../AbstractAddImportFeatureService.cs | 2 +- .../AddImport/SymbolReferenceFinder.cs | 5 ++--- .../Configuration/ConfigurationUpdater.cs | 4 ++-- .../AbstractSuppressionBatchFixAllProvider.cs | 2 +- ...SuppressionCodeFixProvider.PragmaHelpers.cs | 4 ++-- ...r.RemoveSuppressionCodeAction.BatchFixer.cs | 2 +- .../CodeLens/CodeLensFindReferenceProgress.cs | 2 +- .../CodeRefactorings/CodeRefactoringService.cs | 2 +- .../AbstractChangeNamespaceService.cs | 2 +- ...ncNamespaceCodeRefactoringProvider.State.cs | 2 +- .../Completion/CharacterSetModificationRule.cs | 2 +- .../CompletionService_GetCompletions.cs | 4 ++-- .../AbstractSymbolCompletionProvider.cs | 2 +- ...hodImportCompletionHelper.SymbolComputer.cs | 2 +- .../ExtensionMethodImportCompletionHelper.cs | 2 +- .../Completion/Providers/RecommendedKeyword.cs | 2 +- .../Debugging/AbstractBreakpointResolver.cs | 2 +- .../EditAndContinue/DebuggingSession.cs | 4 ++-- .../DebuggingSessionTelemetry.cs | 2 +- .../EditAndContinueDocumentAnalysesCache.cs | 2 +- .../EditAndContinueMethodDebugInfoReader.cs | 2 +- .../EditAndContinue/EditAndContinueService.cs | 2 +- .../Portable/EditAndContinue/EditSession.cs | 4 ++-- .../Core/Portable/EditAndContinue/TraceLog.cs | 2 +- .../Portable/ExtractMethod/MethodExtractor.cs | 2 +- ...dUsagesService.DefinitionTrackingContext.cs | 2 +- ...actFindUsagesService_FindImplementations.cs | 6 +++--- ...shCodeFromMembersCodeRefactoringProvider.cs | 2 +- ...izedMemberService.AbstractInvocationInfo.cs | 2 +- .../AbstractGenerateTypeService.Editor.cs | 10 +++++----- .../InheritanceMargin/InheritanceMarginItem.cs | 2 +- .../Portable/InlineHints/InlineHintHelpers.cs | 2 +- ...eMethodRefactoringProvider.InlineContext.cs | 2 +- .../AbstractStructuralTypeDisplayService.cs | 8 ++++---- .../MetadataAsSourceFileService.cs | 2 +- .../MetadataAsSourceGeneratedFileInfo.cs | 2 +- ...gateToSearchService.CachedDocumentSearch.cs | 2 +- ...eToSearchService.GeneratedDocumentSearch.cs | 2 +- ...ractNavigateToSearchService.NormalSearch.cs | 4 ++-- .../Portable/NavigateTo/NavigateToSearcher.cs | 2 +- .../AbstractPullMemberUpRefactoringProvider.cs | 2 +- .../Portable/PullMemberUp/MembersPuller.cs | 2 +- .../CommonSemanticQuickInfoProvider.cs | 4 ++-- .../Portable/QuickInfo/IndentationHelper.cs | 2 +- .../Shared/Extensions/DocumentExtensions.cs | 2 +- .../Extensions/ISymbolExtensions_Sorting.cs | 3 +-- .../AbstractSignatureHelpProvider.cs | 2 +- .../StackTraceExplorerService.cs | 2 +- .../UnusedReferencesRemover.cs | 5 ++--- .../ValueTrackingProgressCollector.cs | 2 +- .../FileWatching/LspFileChangeWatcher.cs | 2 +- .../FileWatching/SimpleFileChangeWatcher.cs | 2 +- .../LanguageServerProjectSystem.cs | 2 +- .../HostWorkspace/ProjectDependencyHelper.cs | 2 +- .../HostWorkspace/WorkspaceProject.cs | 6 +++--- .../Handler/Restore/RestoreHandler.cs | 2 +- .../Testing/TestDiscoverer.DiscoveryHandler.cs | 2 +- .../LanguageServerEndpointAttribute.cs | 2 +- .../Protocol/DefaultCapabilitiesProvider.cs | 2 +- .../Protocol/Extensions/ProtocolConversions.cs | 2 +- .../CodeFixService.ProjectCodeFixProvider.cs | 2 +- .../DecompiledSource/AssemblyResolver.cs | 2 +- ...nalyzer.InProcOrRemoteHostAnalyzerRunner.cs | 2 +- ...rementalAnalyzer.StateManager.HostStates.cs | 2 +- .../UnifiedSuggestedActionsSource.cs | 6 ++---- .../Handler/CodeActions/CodeActionHelpers.cs | 8 ++++---- .../Highlights/DocumentHighlightHandler.cs | 4 ++-- .../OnAutoInsert/OnAutoInsertHandler.cs | 2 +- .../GetTextDocumentWithContextHandler.cs | 2 +- .../Handler/References/FindUsagesLSPContext.cs | 4 ++-- .../SemanticTokens/SemanticTokensSchema.cs | 2 +- .../Protocol/LspServices/LspServices.cs | 2 +- .../Completion/CompletionFeaturesTests.cs | 8 ++++---- .../Symbols/DocumentSymbolsTests.cs | 2 +- ...ditAndContinueMethodDebugInfoReaderTests.cs | 2 +- .../ActiveStatementsDescription.cs | 4 ++-- .../EditAndContinue/SourceMarkers.cs | 2 +- .../Core/InteractiveHostPlatformInfo.cs | 2 +- .../Interactive/Core/RemoteExecutionResult.cs | 4 ++-- .../Core/RemoteInitializationResult.cs | 4 ++-- .../InteractiveSessionReferencesTests.cs | 4 ++-- src/Scripting/Core/Hosting/SynchronizedList.cs | 2 +- .../EditAndContinue/EditAndContinueTest.cs | 4 ++-- .../PdbUtilities/Reader/SymReaderFactory.cs | 2 +- .../Reader/Token2SourceLineExporter.cs | 2 +- src/Tools/BuildActionTelemetryTable/Program.cs | 5 ++--- src/Tools/BuildValidator/CompilationDiff.cs | 8 ++++---- src/Tools/BuildValidator/Program.cs | 4 ++-- .../Razor/RazorSpanMappingServiceWrapper.cs | 2 +- src/Tools/Replay/Replay.cs | 2 +- .../GenerateFilteredReferenceAssembliesTask.cs | 8 ++++---- src/Tools/Source/RunTests/AssemblyScheduler.cs | 4 ++-- src/Tools/Source/RunTests/Program.cs | 2 +- src/Tools/Source/RunTests/TestRunner.cs | 2 +- ...rpChangeSignatureViewModelFactoryService.cs | 2 +- .../SettingsEditorControl.xaml.cs | 2 +- .../VisualStudioErrorReportingService.cs | 2 +- .../Implementation/VsRefactorNotifyService.cs | 4 ++-- .../Def/Progression/GraphNodeIdCreation.cs | 18 +++++++++--------- .../Dialog/UnusedReferencesTableProvider.cs | 2 +- .../Impl/CodeModel/FileCodeModel_CodeGen.cs | 2 +- .../Options/AbstractOptionPreviewViewModel.cs | 4 ++-- .../NamingStyleOptionPageControl.xaml.cs | 4 ++-- .../Services/ServiceHubServicesTests.cs | 2 +- .../InProcess/EditorInProcess.cs | 2 +- .../InProcess/ScreenshotInProcess.cs | 4 ++-- .../Impl/Client/LanguageServiceUtils.cs | 2 +- .../Panels/WorkspacePanel.xaml.cs | 2 +- .../Definitions/GoToDefinitionHandler.cs | 6 +++--- .../CSharpSyntaxFormattingService.cs | 15 +++++++++------ ...onService.NodesAndTokensToReduceComputer.cs | 2 +- .../Build/ProjectBuildManager.cs | 2 +- .../MSBuild/ProjectFile/Extensions.cs | 2 +- .../MSBuild/ProjectFile/ProjectFile.cs | 4 ++-- .../MSBuild/MSBuild/BuildHostProcessManager.cs | 2 +- .../Core/MSBuild/MSBuild/MSBuildWorkspace.cs | 2 +- .../AbstractCaseCorrectionService.cs | 2 +- .../Classification/ClassifierHelper.cs | 2 +- .../CodeCleanup/AbstractCodeCleanerService.cs | 4 ++-- .../DiagnosticAnalysisResultBuilder.cs | 4 ++-- .../Core/Portable/Diagnostics/Extensions.cs | 2 +- .../Portable/Editing/ImportAdderService.cs | 2 +- .../FindReferences/DependentProjectsFinder.cs | 6 +++--- .../FindReferences/DependentTypeFinder.cs | 4 ++-- ...encesSearchEngine.BidirectionalSymbolSet.cs | 2 +- ...rencesSearchEngine.NonCascadingSymbolSet.cs | 2 +- ...ncesSearchEngine.UnidirectionalSymbolSet.cs | 2 +- .../Finders/AbstractReferenceFinder.cs | 2 +- .../FindSymbols/StreamingProgressCollector.cs | 2 +- ...ymbolFinder.FindReferencesServerCallback.cs | 2 +- .../Core/Portable/FindSymbols/SymbolFinder.cs | 2 +- .../LinkedFileDiffMergingSession.cs | 2 +- .../Core/Portable/Log/AggregateLogger.cs | 6 +++--- .../ConflictEngine/ConflictResolver.Session.cs | 11 +++++++---- .../ConflictEngine/RenamedSpansTracker.cs | 4 ++-- src/Workspaces/Core/Portable/Rename/Renamer.cs | 4 ++-- .../Shared/Extensions/ISymbolExtensions.cs | 2 +- ...ypeSymbolExtensions.AnonymousTypeRemover.cs | 2 +- ...tensions.UnavailableTypeParameterRemover.cs | 2 +- ...SymbolExtensions.UnnamedErrorTypeRemover.cs | 2 +- .../TestHooks/AsynchronousOperationListener.cs | 2 +- .../AsynchronousOperationListenerProvider.cs | 2 +- .../IPersistentStorageConfiguration.cs | 2 +- ...SystemProject.BatchingDocumentCollection.cs | 2 +- .../Solution/ProjectDependencyGraph.cs | 8 ++++---- ...ojectDependencyGraph_AddProjectReference.cs | 2 +- .../Solution/SolutionCompilationState.cs | 4 ++-- .../Workspace/Solution/SolutionState.cs | 2 +- .../CoreTest/BatchFixAllProviderTests.cs | 2 +- .../EditorConfigFileParserTests.cs | 2 +- ...eclarationsTests.TestSolutionsAndProject.cs | 2 +- .../CoreTest/SolutionTests/SolutionTests.cs | 2 +- .../SolutionTests/TryApplyChangesTests.cs | 2 +- .../CoreTestUtilities/MEF/TestComposition.cs | 6 +++--- .../Workspaces/TestWorkspace_XmlConsumption.cs | 10 +++++----- .../Workspaces/TestWorkspace`1.cs | 2 +- ...moteAsynchronousOperationListenerService.cs | 4 ++-- .../RemoteSemanticClassificationService.cs | 2 +- .../Indentation/CSharpSmartTokenFormatter.cs | 3 +-- .../Extensions/INamedTypeSymbolExtensions.cs | 4 ++-- .../SymbolUsageAnalysis.AnalysisData.cs | 2 +- ...s.DataFlowAnalyzer.FlowGraphAnalysisData.cs | 4 ++-- .../FileBannerFacts/AbstractFileBannerFacts.cs | 2 +- .../Core/Utilities/PathMetadataUtilities.cs | 2 +- .../RestrictedInternalsVisibleToAttribute.cs | 2 +- .../CSharpCodeGenerationService.cs | 4 ++-- .../CodeGeneration/CodeGenerationHelpers.cs | 4 ++-- .../CodeGenerationAbstractMethodSymbol.cs | 2 +- .../CodeGenerationAbstractNamedTypeSymbol.cs | 2 +- .../Core/Helpers/MefHostServicesHelpers.cs | 2 +- 204 files changed, 318 insertions(+), 316 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs index 7c3a5d350984d..b657d4cd0f720 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs @@ -69,7 +69,7 @@ public static void AddOptionMapping(string diagnosticId, ImmutableHashSet new ConcurrentDictionary>()); - AddOptionMapping(map, diagnosticId, languageGroup.ToImmutableHashSet()); + AddOptionMapping(map, diagnosticId, [.. languageGroup]); } } } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs index 690ac016a84b8..09655f4fec19a 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs @@ -40,7 +40,7 @@ internal abstract class AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.Empty.Add(statement), + nodesToInsert: [statement], formattingOptions, cancellationToken), DoStatementSyntax doStatementNode => AddBraceToDoStatement(services, root, doStatementNode, formattingOptions, statement, cancellationToken), @@ -195,7 +195,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement oldNode: doStatementNode, newNode: AddBlockToEmbeddedStatementOwner(doStatementNode, formattingOptions), anchorNode: doStatementNode, - nodesToInsert: ImmutableArray.Empty.Add(innerStatement), + nodesToInsert: [innerStatement], formattingOptions, cancellationToken); } @@ -251,7 +251,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement ifStatementNode, AddBlockToEmbeddedStatementOwner(ifStatementNode, formattingOptions), ifStatementNode, - ImmutableArray.Empty.Add(innerStatement), + [innerStatement], formattingOptions, cancellationToken); } @@ -319,7 +319,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( elseClauseNode, WithBraces(elseClauseNode, formattingOptions), elseClauseNode.Parent!, - ImmutableArray.Empty.Add(innerStatement), + [innerStatement], formattingOptions, cancellationToken); } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs index 5cd92953a1c02..482304aa00701 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs @@ -52,7 +52,7 @@ public RenameDashboard( _model = model; InitializeComponent(); - _tabNavigableChildren = new UIElement[] { this.OverloadsCheckbox, this.CommentsCheckbox, this.StringsCheckbox, this.FileRenameCheckbox, this.PreviewChangesCheckbox, this.ApplyButton, this.CloseButton }.ToList(); + _tabNavigableChildren = [this.OverloadsCheckbox, this.CommentsCheckbox, this.StringsCheckbox, this.FileRenameCheckbox, this.PreviewChangesCheckbox, this.ApplyButton, this.CloseButton]; _textView = textView; this.DataContext = model; diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs index a0bed1d52547c..66d9d95cff3a8 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs @@ -96,7 +96,7 @@ private IWpfTextView CreateView(ITextBuffer buffer) private IProjectionBuffer CreateBuffer() { return _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( - _editorOptionsService.Factory.GlobalOptions, _contentType, _spans.ToArray()); + _editorOptionsService.Factory.GlobalOptions, _contentType, [.. _spans]); } } } diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs index c8c5859d075d5..f025bbca1dd3d 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs @@ -436,7 +436,7 @@ private ImmutableArray GetUncommentedSpansInSelection() } } - return uncommentedSpans.ToImmutableArray(); + return [.. uncommentedSpans]; } } } diff --git a/src/EditorFeatures/Core/Editor/TextEditApplication.cs b/src/EditorFeatures/Core/Editor/TextEditApplication.cs index c4bf5575e8f83..c0acaa4266745 100644 --- a/src/EditorFeatures/Core/Editor/TextEditApplication.cs +++ b/src/EditorFeatures/Core/Editor/TextEditApplication.cs @@ -17,7 +17,7 @@ internal static void UpdateText(SourceText newText, ITextBuffer buffer, EditOpti var oldSnapshot = buffer.CurrentSnapshot; var oldText = oldSnapshot.AsText(); var changes = newText.GetTextChanges(oldText); - UpdateText(changes.ToImmutableArray(), buffer, oldSnapshot, oldText, options); + UpdateText([.. changes], buffer, oldSnapshot, oldText, options); } public static void UpdateText(ImmutableArray textChanges, ITextBuffer buffer, EditOptions options) diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs index ead1708f6bec9..0395dfb45c7fc 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs @@ -93,7 +93,7 @@ private static ISettingsProviderFactory GetOptionsProviderFactory(Workspac TryAddProviderForLanguage(LanguageNames.VisualBasic, workspace, providers); } - return new CombinedOptionsProviderFactory(providers.ToImmutableArray()); + return new CombinedOptionsProviderFactory([.. providers]); static void TryAddProviderForLanguage(string language, Workspace workspace, List> providers) { diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs index fb310e0fc78f7..b1a04b9621a9f 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs @@ -88,7 +88,7 @@ public ImmutableArray GetCurrentDataSnapshot() { lock (s_gate) { - return _snapshot.ToImmutableArray(); + return [.. _snapshot]; } } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index fe13da5eaac87..4f30186d98c73 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -319,7 +319,7 @@ private void UpdateReferenceLocationsTask() // https://github.com/dotnet/roslyn/issues/40890 await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - RaiseSessionSpansUpdated(inlineRenameLocations.Locations.ToImmutableArray()); + RaiseSessionSpansUpdated([.. inlineRenameLocations.Locations]); return inlineRenameLocations; }); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs index 2f738faae61df..df4f024f6bedc 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -82,7 +82,7 @@ internal CompletionSource( _asyncListener = asyncListener; _editorOptionsService = editorOptionsService; _isDebuggerTextView = textView is IDebuggerTextView; - _roles = textView.Roles.ToImmutableHashSet(); + _roles = [.. textView.Roles]; } public AsyncCompletionData.CompletionStartData InitializeCompletion( diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index fafe7b3331b95..0bdb6a8b06e80 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -117,7 +117,7 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa Range = true, Legend = new SemanticTokensLegend { - TokenTypes = SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes.ToArray(), + TokenTypes = [.. SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes], TokenModifiers = SemanticTokensSchema.TokenModifiers } }; diff --git a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs index abce7a9ad1c88..385423e295bc6 100644 --- a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs @@ -215,13 +215,13 @@ private void GetProjectItems(out ImmutableArray projec return; } - projectItems = documents.Select(d => + projectItems = [.. documents.Select(d => new NavigationBarProjectItem( d.Project.Name, d.Project.GetGlyph(), workspace: d.Project.Solution.Workspace, documentId: d.Id, - language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToImmutableArray(); + language: d.Project.Language)).OrderBy(projectItem => projectItem.Text)]; var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); selectedProjectItem = document != null diff --git a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs index 75a264060fcc5..fae0c03b08895 100644 --- a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs +++ b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs @@ -589,7 +589,7 @@ public Task> CreateRemovedAnalyzerCo oldBuffer.CurrentSnapshot, "...", description, - originalSpans.ToArray()); + [.. originalSpans]); var changedBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, @@ -597,7 +597,7 @@ public Task> CreateRemovedAnalyzerCo newBuffer.CurrentSnapshot, "...", description, - changedSpans.ToArray()); + [.. changedSpans]); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return await CreateNewDifferenceViewerAsync(leftWorkspace, rightWorkspace, originalBuffer, changedBuffer, zoomLevel, cancellationToken); diff --git a/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs b/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs index 687c123b63ba3..8541cd06e00ff 100644 --- a/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs @@ -48,7 +48,7 @@ public void TestNoCyclesInFixProviders() var vbProviders = providersPerLanguage[LanguageNames.VisualBasic]; ExtensionOrderer.TestAccessor.CheckForCycles(vbProviders); - actualOrder = ExtensionOrderer.Order(vbProviders).ToArray(); + actualOrder = [.. ExtensionOrderer.Order(vbProviders)]; Assert.True(actualOrder.Length > 0); Assert.True(actualOrder.IndexOf(p => p.Metadata.Name == PredefinedCodeFixProviderNames.AddImport) < actualOrder.IndexOf(p => p.Metadata.Name == PredefinedCodeFixProviderNames.FullyQualify)); @@ -106,7 +106,7 @@ public void TestNoCyclesInRefactoringProviders() var vbProviders = providersPerLanguage[LanguageNames.VisualBasic]; ExtensionOrderer.TestAccessor.CheckForCycles(vbProviders); - actualOrder = ExtensionOrderer.Order(vbProviders).ToArray(); + actualOrder = [.. ExtensionOrderer.Order(vbProviders)]; Assert.True(actualOrder.Length > 0); } diff --git a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs index ca2b47721d8d9..434fcf3f2cd69 100644 --- a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs +++ b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs @@ -61,7 +61,7 @@ public void AssemblyAndPdb(DebugInformationFormat format) Stream? currentPdbStream = null; var outputs = new TestCompilationOutputs( - openAssemblyStream: () => currentPEStream = new MemoryStream(peImage.ToArray()), + openAssemblyStream: () => currentPEStream = new MemoryStream([.. peImage]), openPdbStream: () => { if (pdbStream == null) diff --git a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs index 793df59acf65f..00ceef681f06d 100644 --- a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs +++ b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs @@ -339,7 +339,7 @@ private static async Task> GetTagsFromWorkspaceAsyn var context = new TaggerContext(document, view.TextSnapshot, frozenPartialSemantics: false); await provider.GetTestAccessor().ProduceTagsAsync(context); - return context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start).ToList(); + return [.. context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start)]; } #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs index 09da0aa6cb580..c364f5a0ffd40 100644 --- a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs +++ b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs @@ -35,7 +35,7 @@ public void Dispose() lock (_tasks) { _tokenSource.Cancel(); - tasks = _tasks.ToArray(); + tasks = [.. _tasks]; } try diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index 6ebfbc1b253d4..d8fb050b92a0e 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -68,7 +68,10 @@ public void TestCreateTextUsesByteOrderMarkIfPresent() TestCreateTextInferredEncoding( textFactoryService, - Encoding.UTF8.GetPreamble().Concat(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes("Test")).ToArray(), + [ + .. Encoding.UTF8.GetPreamble(), + .. new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes("Test"), + ], defaultEncoding: Encoding.GetEncoding(1254), expectedEncoding: Encoding.UTF8); } diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index 5685f62e13433..0a13a760a93b9 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -162,7 +162,7 @@ internal void InitializeWorkspace(EditorTestWorkspace workspace) protected static void VerifyNavigateToResultItems( List expecteditems, IEnumerable items) { - expecteditems = expecteditems.OrderBy(i => i.Name).ToList(); + expecteditems = [.. expecteditems.OrderBy(i => i.Name)]; items = items.OrderBy(i => i.Name).ToList(); Assert.Equal(expecteditems.Count(), items.Count()); diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs index 27a7d762f5b1c..1c629b457c386 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs @@ -382,9 +382,9 @@ public void BadPdb_ForwardChain() { switch (token) { - case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes.ToArray(); - case methodToken2: return new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes.ToArray(); - case methodToken3: return new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes.ToArray(); + case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes]; + case methodToken2: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes]; + case methodToken3: return [.. new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes]; default: throw null; } }); @@ -421,7 +421,7 @@ public void BadPdb_Cycle() { switch (token) { - case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes.ToArray(); + case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes]; default: throw null; } }); diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs index 89e03e291c67e..b87194ad1065d 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs @@ -91,7 +91,7 @@ public void Free() public T[] ToArray() { - return _items.ToArray(); + return [.. _items]; } public T[] ToArrayAndFree() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs index adbf1368e2732..1da9a346b6325 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs @@ -40,7 +40,7 @@ internal void AddModule(Module module) internal Module[] GetModules() { - return _modules.ToArray(); + return [.. _modules]; } void IDisposable.Dispose() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs index 6f9f3d7ad366f..04651d7b9dc9a 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs @@ -73,7 +73,7 @@ internal override Request[] GetRequests(Process process) { return new Request[0]; } - return requests.ToArray(); + return [.. requests]; } internal override string GetRequestModuleName(Request request) diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs index c46088fa8a4c1..4f43d28015f4f 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs @@ -16,7 +16,7 @@ internal static class ReflectionUtilities { internal static Assembly Load(ImmutableArray assembly) { - return Assembly.Load(assembly.ToArray()); + return Assembly.Load([.. assembly]); } internal static object Instantiate(this Type type, params object[] args) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs index e6f199978569a..60ebe77ff745b 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs @@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal partial class DeclarationNameCompletionProvider([ImportMany] IEnumerable> recommenders) : LSPCompletionProvider { - private ImmutableArray> Recommenders { get; } = ExtensionOrderer.Order(recommenders).ToImmutableArray(); + private ImmutableArray> Recommenders { get; } = [.. ExtensionOrderer.Order(recommenders)]; internal override string Language => LanguageNames.CSharp; diff --git a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs index edfc5c4e75f64..ea9130d85ec3a 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs @@ -827,7 +827,7 @@ private StatementSyntax[] GenerateStatements( // The stack was processed in the reverse order, but the extra statements should be provided in the direct order. statements.Reverse(); statements.Add(statement.WithAdditionalAnnotations(Simplifier.Annotation)); - return statements.ToArray(); + return [.. statements]; } private bool TryProcessQueryBody(QueryBodySyntax queryBody, QueryExpressionProcessingInfo queryExpressionProcessingInfo) diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs index ea8ae8a8da756..54be9ac17f079 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs @@ -124,8 +124,8 @@ void Convert(ExpressionSyntax replacingExpression, SyntaxNode nodeToRemoveIfFoll // Output: // return queryGenerated.ToList(); or return queryGenerated.Count(); replacingExpression = returnStatement.Expression; - leadingTrivia = GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) - .Concat(SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)).ToArray(); + leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) +, .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; editor.RemoveNode(nodeToRemoveIfFollowedByReturn); } else diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 6cedb5812a4f7..617ca5843708c 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -240,7 +240,7 @@ ImmutableDictionary CreateSynthesizedFields() } } - return result.ToImmutableHashSet(); + return [.. result]; } void RemovePrimaryConstructorParameterList() diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index b2c1e3974176b..11ec3287f89a2 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -927,7 +927,7 @@ private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldN RoslynDebug.Assert(oldTokens != null); RoslynDebug.Assert(newTokens != null); - return DeclareSameIdentifiers(oldTokens.ToArray(), newTokens.ToArray()); + return DeclareSameIdentifiers([.. oldTokens], [.. newTokens]); } protected override bool AreEquivalentImpl(SyntaxToken oldToken, SyntaxToken newToken) diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index 06b21c73514f7..24f428b4d8983 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -59,7 +59,7 @@ protected override ImmutableArray GetCapturedTypeParameter type.GetReferencedTypeParameters(result); } - return result.ToImmutableArray(); + return [.. result]; } protected override ImmutableArray GenerateTypeParameters(CancellationToken cancellationToken) diff --git a/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs b/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs index 2c06e936fc06a..3e439cc5f6b4e 100644 --- a/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs +++ b/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs @@ -156,15 +156,15 @@ private static QuickInfoItem CreateQuickInfo(TextSpan location, DiagnosticDescri var idTag = !string.IsNullOrWhiteSpace(descriptor.HelpLinkUri) ? new TaggedText(TextTags.Text, descriptor.Id, TaggedTextStyle.None, descriptor.HelpLinkUri, descriptor.HelpLinkUri) : new TaggedText(TextTags.Text, descriptor.Id); - return QuickInfoItem.Create(location, sections: new[] - { - QuickInfoSection.Create(QuickInfoSectionKinds.Description, new[] - { + return QuickInfoItem.Create(location, sections: + [ + QuickInfoSection.Create(QuickInfoSectionKinds.Description, + [ idTag, new TaggedText(TextTags.Punctuation, ":"), new TaggedText(TextTags.Space, " "), new TaggedText(TextTags.Text, description) - }.ToImmutableArray()) - }.ToImmutableArray(), relatedSpans: relatedSpans.ToImmutableArray()); + ]) + ], relatedSpans: [.. relatedSpans]); } } diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 7d9e4f7c66ec5..9e42352af2e27 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -27,7 +27,7 @@ internal static void VerifyLineEdits( VerifyLineEdits( editScript, - new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, lineEdits.ToImmutableArray()) }, + new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, [.. lineEdits]) }, semanticEdits, diagnostics, capabilities); diff --git a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1eac532fdd3bb..7967d1f73cb8a 100644 --- a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3679,7 +3679,7 @@ public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithBodyAndMet expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); edits.VerifySemantics( - expectedEdits.ToArray(), + [.. expectedEdits], capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 99094f7dcdd97..0a80ac10649b0 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -192,7 +192,7 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } } - return allReferences.ToImmutableArray(); + return [.. allReferences]; } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 57508f95172f4..0225e66444854 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -138,12 +138,11 @@ private async Task> DoAsync(SearchScope searchSc private ImmutableArray DeDupeAndSortReferences(ImmutableArray allReferences) { - return allReferences + return [.. allReferences .Distinct() .Where(NotNull) .Where(NotGlobalNamespace) - .OrderBy((r1, r2) => r1.CompareTo(_document, r2)) - .ToImmutableArray(); + .OrderBy((r1, r2) => r1.CompareTo(_document, r2))]; } private static void CalculateContext( diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 2dfef354517ad..3c1d7a5391de9 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -392,10 +392,10 @@ internal static ImmutableArray GetCodeStyleOptionsForDiagnostic(Diagno { if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions(diagnostic.Id, project.Language, out var options)) { - return (from option in options + return [.. (from option in options where option.DefaultValue is ICodeStyleOption orderby option.Definition.ConfigName - select option).ToImmutableArray(); + select option)]; } return []; diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs index 9bb5419aa19c0..403b33e0bad50 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs @@ -110,7 +110,7 @@ internal abstract class AbstractSuppressionBatchFixAllProvider : FixAllProvider await Task.WhenAll(tasks).ConfigureAwait(false); } - return fixesBag.ToImmutableArray(); + return [.. fixesBag]; } private async Task AddDocumentFixesAsync( diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs index c748295d4eabb..e0c7a073532e0 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs @@ -164,11 +164,11 @@ internal static SyntaxToken GetNewEndTokenWithAddedPragma( var isEOF = fixer.IsEndOfFileToken(endToken); if (isEOF) { - trivia = endToken.LeadingTrivia.ToImmutableArray(); + trivia = [.. endToken.LeadingTrivia]; } else { - trivia = endToken.TrailingTrivia.ToImmutableArray(); + trivia = [.. endToken.TrailingTrivia]; } var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: false, triviaAtIndex: out var insertBeforeTrivia); diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs index c03dc33e08ad9..41b29ab486f0a 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs @@ -153,7 +153,7 @@ public override async Task TryGetMergedFixAsync( } return await base.TryGetMergedFixAsync( - newBatchOfFixes.ToImmutableArray(), fixAllState, progressTracker, cancellationToken).ConfigureAwait(false); + [.. newBatchOfFixes], fixAllState, progressTracker, cancellationToken).ConfigureAwait(false); } private static async Task> GetAttributeNodesToFixAsync(ImmutableArray attributeRemoveFixes, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs index 076e4ddaabbdb..5a0a535ac915c 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs @@ -48,7 +48,7 @@ internal sealed class CodeLensFindReferencesProgress( public int ReferencesCount => _locations.Count; - public ImmutableArray Locations => _locations.ToImmutableArray(); + public ImmutableArray Locations => [.. _locations]; public void OnStarted() { diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 77cffe24feeb2..b8daab42b776b 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -212,7 +212,7 @@ private class ProjectCodeRefactoringProvider : AbstractProjectExtensionProvider { protected override ImmutableArray GetLanguages(ExportCodeRefactoringProviderAttribute exportAttribute) - => exportAttribute.Languages.ToImmutableArray(); + => [.. exportAttribute.Languages]; protected override bool TryGetExtensionsFromReference(AnalyzerReference reference, out ImmutableArray extensions) { diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 6a945ab96565f..8845c42bdabdb 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -259,7 +259,7 @@ await ChangeNamespaceInSingleDocumentAsync(solutionAfterNamespaceChange, documen solutionAfterImportsRemoved = await RemoveUnnecessaryImportsAsync( solutionAfterImportsRemoved, - referenceDocuments.ToImmutableArray(), + [.. referenceDocuments], [declaredNamespace, targetNamespace], fallbackOptions, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs index 9f9afa686ebeb..5a7dbf9fb95b9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs @@ -158,7 +158,7 @@ private static bool IsDocumentPathRootedInProjectFolder(Document document) if (projectRoot is null) return false; - var folderPath = Path.Combine(document.Folders.ToArray()); + var folderPath = Path.Combine([.. document.Folders]); var logicalDirectoryPath = PathUtilities.CombineAbsoluteAndRelativePaths(projectRoot, folderPath); if (logicalDirectoryPath is null) return false; diff --git a/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs b/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs index 9115fa8976a73..b05169033da52 100644 --- a/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs +++ b/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs @@ -43,5 +43,5 @@ public static CharacterSetModificationRule Create(CharacterSetModificationKind k /// One or more characters. These are typically punctuation characters. /// public static CharacterSetModificationRule Create(CharacterSetModificationKind kind, params char[] characters) - => new(kind, characters.ToImmutableArray()); + => new(kind, [.. characters]); } diff --git a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs index 9c707d7f8cb70..5095803afa12f 100644 --- a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs +++ b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs @@ -137,13 +137,13 @@ ImmutableArray GetTriggeredProviders( var triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(document.Project.Services, text, caretPosition, trigger, options, passThroughOptions)).ToImmutableArrayOrEmpty(); Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options)); - return triggeredProviders.IsEmpty ? providers.ToImmutableArray() : triggeredProviders; + return triggeredProviders.IsEmpty ? [.. providers] : triggeredProviders; } return []; default: - return providers.ToImmutableArray(); + return [.. providers]; } } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index f3a276a0b9718..8417361ead2dd 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs @@ -277,7 +277,7 @@ private async Task> GetItemsAsync( var totalProjects = contextAndSymbolLists.Select(t => t.documentId.ProjectId).ToList(); return CreateItems( - completionContext, symbolToContextMap.Keys.ToImmutableArray(), symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects); + completionContext, [.. symbolToContextMap.Keys], symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects); } protected virtual bool IsExclusive() diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs index 28b81bce83aa4..1231d66758f4b 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs @@ -482,7 +482,7 @@ private static ImmutableArray GetReceiverTypeNames(ITypeSymbol receiverT { using var _ = PooledHashSet.GetInstance(out var allTypeNamesBuilder); AddNamesForTypeWorker(receiverTypeSymbol, allTypeNamesBuilder); - return allTypeNamesBuilder.ToImmutableArray(); + return [.. allTypeNamesBuilder]; static void AddNamesForTypeWorker(ITypeSymbol receiverTypeSymbol, PooledHashSet builder) { diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 58af5842ea690..1e7f0aed56232 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -72,7 +72,7 @@ public static void WarmUpCacheInCurrentProcess(Project project) var remoteResult = await client.TryInvokeAsync( project, (service, solutionInfo, cancellationToken) => service.GetUnimportedExtensionMethodsAsync( - solutionInfo, document.Id, position, receiverTypeSymbolKeyData, namespaceInScope.ToImmutableArray(), + solutionInfo, document.Id, position, receiverTypeSymbolKeyData, [.. namespaceInScope], targetTypesSymbolKeyData, forceCacheCreation, hideAdvancedMembers, cancellationToken), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs b/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs index 869ffdd646947..a4739b0a46790 100644 --- a/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs +++ b/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs @@ -40,6 +40,6 @@ internal static ImmutableArray CreateDisplayParts(string keyw textContentBuilder.AddText(toolTip); } - return textContentBuilder.ToImmutableArray(); + return [.. textContentBuilder]; } } diff --git a/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs b/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs index 1bdb4ab26c64d..7e41ac5d80b2f 100644 --- a/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs +++ b/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs @@ -147,7 +147,7 @@ private async Task> FindMembersAsync( default: // They have a namespace or nested type qualified name. Walk up to the root namespace trying to match. var containers = await _solution.GetGlobalNamespacesAsync(cancellationToken).ConfigureAwait(false); - return FindMembers(containers, nameParts.ToArray()); + return FindMembers(containers, [.. nameParts]); } } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index 071b40d5e7440..ced9d33433ef3 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -435,7 +435,7 @@ private static ImmutableDictionary> GroupToImmutableDiction foreach (var item in items) { - builder.Add(item.Key, item.ToImmutableArray()); + builder.Add(item.Key, [.. item]); } return builder.ToImmutable(); @@ -841,7 +841,7 @@ public ImmutableHashSet GetModulesPreparedForUpdate() { lock (_instance._modulesPreparedForUpdateGuard) { - return _instance._modulesPreparedForUpdate.ToImmutableHashSet(); + return [.. _instance._modulesPreparedForUpdate]; } } diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs index 2b3d5186583d6..2a9b478b6a437 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs @@ -15,7 +15,7 @@ internal sealed class DebuggingSessionTelemetry(Guid solutionSessionId) internal readonly struct Data(DebuggingSessionTelemetry telemetry) { public readonly Guid SolutionSessionId = telemetry._solutionSessionId; - public readonly ImmutableArray EditSessionData = telemetry._editSessionData.ToImmutableArray(); + public readonly ImmutableArray EditSessionData = [.. telemetry._editSessionData]; public readonly int EmptyEditSessionCount = telemetry._emptyEditSessionCount; public readonly int EmptyHotReloadEditSessionCount = telemetry._emptyHotReloadEditSessionCount; } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs index 90c463d540cf9..9167133f77cbf 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -45,7 +45,7 @@ public async ValueTask> GetDocumentAnaly var tasks = documents.Select(document => Task.Run(() => GetDocumentAnalysisAsync(oldSolution, document.oldDocument, document.newDocument, activeStatementSpanProvider, cancellationToken).AsTask(), cancellationToken)); var allResults = await Task.WhenAll(tasks).ConfigureAwait(false); - return allResults.ToImmutableArray(); + return [.. allResults]; } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs index 1d69bf53c5dd6..c62f2c83f7ef4 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs @@ -236,7 +236,7 @@ internal static bool TryGetDocumentChecksum(ISymUnmanagedReader5 symReader, stri } algorithmId = symDocument.GetHashAlgorithm(); - checksum = symDocument.GetChecksum().ToImmutableArray(); + checksum = [.. symDocument.GetChecksum()]; return true; } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index ee551a3796cd7..c1ceea78751b5 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs @@ -109,7 +109,7 @@ private ImmutableArray GetActiveDebuggingSessions() { lock (_debuggingSessions) { - return _debuggingSessions.ToImmutableArray(); + return [.. _debuggingSessions]; } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index fc558099aebaf..19f640b73f83a 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -750,7 +750,7 @@ internal static void MergePartialEdits( if (edits.Count == mergedEditsBuilder.Count) { mergedEdits = mergedEditsBuilder.ToImmutable(); - addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + addedSymbols = [.. addedSymbolsBuilder]; return; } @@ -804,7 +804,7 @@ internal static void MergePartialEdits( } mergedEdits = mergedEditsBuilder.ToImmutable(); - addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + addedSymbols = [.. addedSymbolsBuilder]; } public async ValueTask EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider solutionActiveStatementSpanProvider, UpdateId updateId, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs index 8c0afaf35a6fd..a96471c54ad53 100644 --- a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs +++ b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs @@ -159,7 +159,7 @@ public void Write(DebuggingSessionId sessionId, ImmutableArray bytes, stri try { path = Path.Combine(CreateSessionDirectory(sessionId, directory), fileName); - File.WriteAllBytes(path, bytes.ToArray()); + File.WriteAllBytes(path, [.. bytes]); } catch (Exception e) { diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index af01a2f0785ee..349b799dcf13d 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -214,7 +214,7 @@ private static async Task ExpandAsync(TSelectionResult selecti } private ImmutableArray GetFormattingRules(Document document) - => ImmutableArray.Create(GetCustomFormattingRule(document)).AddRange(Formatter.GetDefaultFormattingRules(document)); + => [GetCustomFormattingRule(document), .. Formatter.GetDefaultFormattingRules(document)]; private OperationStatus CheckVariableTypes( OperationStatus status, diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs index b5c8b72f60d31..30c8390088926 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs @@ -59,7 +59,7 @@ public ImmutableArray GetDefinitions() { lock (_gate) { - return _definitions.ToImmutableArray(); + return [.. _definitions]; } } } diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs index 18ef1f1dd5451..9e2ff048580f5 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs @@ -120,7 +120,7 @@ private static async Task> FindSourceImplementationsAsyn } } - return builder.ToImmutableArray(); + return [.. builder]; static bool AddedAllLocations(ISymbol implementation, HashSet<(string filePath, TextSpan span)> seenLocations) { @@ -153,7 +153,7 @@ private static async Task> FindImplementationsWorkerAsyn } } - return result.ToImmutableArray(); + return [.. result]; } private static async Task> FindSourceAndMetadataImplementationsAsync( @@ -189,7 +189,7 @@ private static async Task> FindSourceAndMetadataImplemen implementationsAndOverrides.Add(symbol); } - return implementationsAndOverrides.ToImmutableArray(); + return [.. implementationsAndOverrides]; } else if (symbol is INamedTypeSymbol { TypeKind: TypeKind.Class } namedType) { diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs index 5ed4e014de204..590f45b35393c 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs @@ -236,7 +236,7 @@ private async Task> CreateActionsAsync( } var codeActions = await Task.WhenAll(tasks).ConfigureAwait(false); - return codeActions.ToImmutableArray(); + return [.. codeActions]; } diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs index 8ffdab80fa802..7cd296978ce7b 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs @@ -62,7 +62,7 @@ private ITypeParameterSymbol MassageTypeParameter( var nonClassTypes = constraints.Where(ts => ts.TypeKind != TypeKind.Class).ToList(); classTypes = MergeClassTypes(classTypes); - constraints = classTypes.Concat(nonClassTypes).ToList(); + constraints = [.. classTypes, .. nonClassTypes]; if (constraints.SequenceEqual(typeParameter.ConstraintTypes)) { return typeParameter; diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs index 48d4dfc643a5f..8adaaeda55199 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs @@ -313,7 +313,7 @@ private async Task> GetGenerateInNewFileOper ? folders : _state.SimpleName != _state.NameOrMemberAccessExpression ? containers.ToList() - : _semanticDocument.Document.Folders.ToList(); + : [.. _semanticDocument.Document.Folders]; if (newDocument.Project.Language == _semanticDocument.Document.Project.Language) { @@ -524,8 +524,8 @@ private async Task> GetGenerateIntoExistingD // Populate the ContainerList AddFoldersToNamespaceContainers(containerList, folders); - containers = containerList.ToArray(); - includeUsingsOrImports = string.Join(".", containerList.ToArray()); + containers = [.. containerList]; + includeUsingsOrImports = string.Join(".", [.. containerList]); } // Case 4 : If the type is generated into the same VB project or @@ -538,8 +538,8 @@ private async Task> GetGenerateIntoExistingD { // Populate the ContainerList AddFoldersToNamespaceContainers(containerList, folders); - containers = containerList.ToArray(); - includeUsingsOrImports = string.Join(".", containerList.ToArray()); + containers = [.. containerList]; + includeUsingsOrImports = string.Join(".", [.. containerList]); if (!string.IsNullOrWhiteSpace(rootNamespaceOfTheProjectGeneratedInto)) { includeUsingsOrImports = string.IsNullOrEmpty(includeUsingsOrImports) diff --git a/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs b/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs index e5fe8ea9ea5f1..4e17a45b3c04e 100644 --- a/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs +++ b/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs @@ -71,5 +71,5 @@ public bool Equals(InheritanceMarginItem other) => targetItems.IsEmpty ? null : new(lineNumber, topLevelDisplayText, displayTexts, glyph, Order(targetItems)); public static ImmutableArray Order(ImmutableArray targetItems) - => targetItems.OrderBy(t => t.DisplayName).ThenByDescending(t => t.LanguageGlyph).ThenBy(t => t.ProjectName ?? "").ToImmutableArray(); + => [.. targetItems.OrderBy(t => t.DisplayName).ThenByDescending(t => t.LanguageGlyph).ThenBy(t => t.ProjectName ?? "")]; } diff --git a/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs b/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs index 1848fe2b0847d..3f7c1c32c7e43 100644 --- a/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs +++ b/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs @@ -54,7 +54,7 @@ private static async Task> GetDescriptionAsync(Docume } } - return parts.ToImmutableArray(); + return [.. parts]; } return default; diff --git a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs index efadd9d050f2d..0d890aadf1f0b 100644 --- a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs +++ b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs @@ -395,7 +395,7 @@ public static ImmutableHashSet GetAllSymbols( var operation = semanticModel.GetOperation(methodDeclarationSyntax, cancellationToken); visitor.Visit(operation); - return visitor._allSymbols.ToImmutableHashSet(); + return [.. visitor._allSymbols]; } public override void Visit(IOperation? operation) diff --git a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs index a6fe4acda7d4e..23e2bab3d707f 100644 --- a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs +++ b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs @@ -148,7 +148,7 @@ private static ImmutableArray OrderStructuralTypes( { if (symbol is IMethodSymbol method) { - return structuralTypes.OrderBy( + return [.. structuralTypes.OrderBy( (n1, n2) => { var index1 = method.TypeArguments.IndexOf(n1); @@ -157,11 +157,11 @@ private static ImmutableArray OrderStructuralTypes( index2 = index2 < 0 ? int.MaxValue : index2; return index1 - index2; - }).ToImmutableArray(); + })]; } else if (symbol is IPropertySymbol property) { - return structuralTypes.OrderBy( + return [.. structuralTypes.OrderBy( (n1, n2) => { if (n1.Equals(property.ContainingType) && !n2.Equals(property.ContainingType)) @@ -176,7 +176,7 @@ private static ImmutableArray OrderStructuralTypes( { return 0; } - }).ToImmutableArray(); + })]; } return structuralTypes; diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index fe3febe0beac0..d0abbcdeb54b1 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -30,7 +30,7 @@ internal class MetadataAsSourceFileService([ImportMany] IEnumerable - private readonly ImmutableArray> _providers = ExtensionOrderer.Order(providers).ToImmutableArray(); + private readonly ImmutableArray> _providers = [.. ExtensionOrderer.Order(providers)]; /// /// Workspace created the first time we generate any metadata for any symbol. diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs index bd27fae1aa8bf..3101d90c3049b 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs @@ -37,7 +37,7 @@ public MetadataAsSourceGeneratedFileInfo(string rootPath, Workspace sourceWorksp ? sourceProject.ParseOptions : sourceProject.Solution.Services.GetLanguageServices(LanguageName).GetRequiredService().GetDefaultParseOptionsWithLatestLanguageVersion(); - this.References = sourceProject.MetadataReferences.ToImmutableArray(); + this.References = [.. sourceProject.MetadataReferences]; this.AssemblyIdentity = topLevelNamedType.ContainingAssembly.Identity; var extension = LanguageName == LanguageNames.CSharp ? ".cs" : ".vb"; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 3a0fd67c501d9..41ba1ca6bbe81 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -84,7 +84,7 @@ public async Task SearchCachedDocumentsAsync( var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); await client.TryInvokeAsync( (service, callbackId, cancellationToken) => - service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 5037c87b9bbc0..ee18144bee5f8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -43,7 +43,7 @@ await client.TryInvokeAsync( // compilations would not be shared and we'd have to rebuild them. solution, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchGeneratedDocumentsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchGeneratedDocumentsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index cfc2f549f72ba..d434504af767c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -35,7 +35,7 @@ public async Task SearchDocumentAsync( await client.TryInvokeAsync( document.Project, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; @@ -81,7 +81,7 @@ await client.TryInvokeAsync( // on the oop side. solution, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchProjectsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), priorityDocumentIds, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchProjectsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), priorityDocumentIds, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index fc90beeb18284..af9ee04c459d0 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -381,7 +381,7 @@ async Task SearchCoreAsync(IGrouping grouping var searchService = grouping.Key; await processProjectAsync( searchService, - grouping.ToImmutableArray(), + [.. grouping], (project, result) => { // If we're seeing a dupe in another project, then filter it out here. The results from diff --git a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs index 193ffd4030719..b036ced7dcf76 100644 --- a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs +++ b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs @@ -104,7 +104,7 @@ private static ImmutableArray FindAllValidDestinations( { var allDestinations = selectedMembers.All(m => m.IsKind(SymbolKind.Field)) ? containingType.GetBaseTypes().ToImmutableArray() - : containingType.AllInterfaces.Concat(containingType.GetBaseTypes()).ToImmutableArray(); + : [.. containingType.AllInterfaces, .. containingType.GetBaseTypes()]; return allDestinations.WhereAsArray(destination => MemberAndDestinationValidator.IsDestinationValid(solution, destination, cancellationToken)); } diff --git a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs index 0ab3b58f2910a..7091430a3c1b0 100644 --- a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs +++ b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs @@ -489,7 +489,7 @@ private static async Task @ref.GetSyntaxAsync(cancellationToken)); var allSyntaxes = await Task.WhenAll(tasks).ConfigureAwait(false); - symbolToDeclarationsBuilder.Add(memberAnalysisResult.Member, allSyntaxes.ToImmutableArray()); + symbolToDeclarationsBuilder.Add(memberAnalysisResult.Member, [.. allSyntaxes]); } return symbolToDeclarationsBuilder.ToImmutableDictionary(); diff --git a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs index d8e20f3fe9c71..73580d5c070a1 100644 --- a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs +++ b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs @@ -164,10 +164,10 @@ protected static Task CreateContentAsync( // if generating quick info for an attribute, prefer bind to the class instead of the constructor if (syntaxFactsService.IsAttributeName(token.Parent!)) { - symbols = symbols.OrderBy((s1, s2) => + symbols = [.. symbols.OrderBy((s1, s2) => s1.Kind == s2.Kind ? 0 : s1.Kind == SymbolKind.NamedType ? -1 : - s2.Kind == SymbolKind.NamedType ? 1 : 0).ToImmutableArray(); + s2.Kind == SymbolKind.NamedType ? 1 : 0)]; } return QuickInfoUtilities.CreateQuickInfoItemAsync( diff --git a/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs b/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs index 09fbb8c8c6fbe..9bbcd5aac43f3 100644 --- a/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs +++ b/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs @@ -73,7 +73,7 @@ public static ImmutableArray GetSpansWithAlignedIndentation( } } - return adjustedClassifiedSpans.ToImmutableArray(); + return [.. adjustedClassifiedSpans]; } else { diff --git a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs index 4e1f9c472db23..4326eef9430da 100644 --- a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -68,7 +68,7 @@ public static async Task> GetUnionItemsFromDocumentAndLinkedDo totalItems.AddRange(values.NullToEmpty()); } - return totalItems.ToImmutableArray(); + return [.. totalItems]; } public static async Task IsValidContextForDocumentOrLinkedDocumentsAsync( diff --git a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs index 8043821cf95c2..c871967799ac1 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs @@ -36,8 +36,7 @@ public static ImmutableArray Sort( var symbolToParameterTypeNames = new ConcurrentDictionary(); string[] getParameterTypeNames(TSymbol s) => GetParameterTypeNames(s, semanticModel, position); - return symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames)) - .ToImmutableArray(); + return [.. symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames))]; } private static INamedTypeSymbol GetNamedType(ITypeSymbol type) diff --git a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs index 9016af082906d..d77c21b9257f6 100644 --- a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs +++ b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs @@ -101,7 +101,7 @@ private static (IList items, int? selectedItem) Filter(IList< var filteredList = items.Where(i => Include(i, parameterNames)).ToList(); var isEmpty = filteredList.Count == 0; if (!selectedItem.HasValue || isEmpty) - return (isEmpty ? items.ToList() : filteredList, selectedItem); + return (isEmpty ? [.. items] : filteredList, selectedItem); // adjust the selected item var selection = items[selectedItem.Value]; diff --git a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs index 0591664252acf..523087d5364de 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs @@ -106,6 +106,6 @@ private static ImmutableArray GetFileMatches(Solution solution, StackF } } - return potentialMatches.ToImmutableArray(); + return [.. potentialMatches]; } } diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index 6b52fabbaec56..f70b8941c613c 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -256,9 +256,8 @@ internal static ImmutableArray GetAllCompilationAssemblies(ReferenceInfo { var transitiveCompilationAssemblies = reference.Dependencies .SelectMany(dependency => GetAllCompilationAssemblies(dependency)); - return reference.CompilationAssemblies - .Concat(transitiveCompilationAssemblies) - .ToImmutableArray(); + return [.. reference.CompilationAssemblies +, .. transitiveCompilationAssemblies]; } public static async Task UpdateReferencesAsync( diff --git a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs index 0720c59837c96..36cc24bc78513 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs @@ -33,7 +33,7 @@ public ImmutableArray GetItems() { lock (_lock) { - return _items.ToImmutableArray(); + return [.. _items]; } } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs index 404e302782684..b2da22f5e8157 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs @@ -42,7 +42,7 @@ public static bool SupportsLanguageServerHost(LanguageServerHost languageServerH public IFileChangeContext CreateContext(params WatchedDirectory[] watchedDirectories) { - return new FileChangeContext(watchedDirectories.ToImmutableArray(), this); + return new FileChangeContext([.. watchedDirectories], this); } private class FileChangeContext : IFileChangeContext diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs index 5b21bd69e817a..3f901a8be52b8 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs @@ -19,7 +19,7 @@ internal sealed class SimpleFileChangeWatcher : IFileChangeWatcher { public IFileChangeContext CreateContext(params WatchedDirectory[] watchedDirectories) { - return new FileChangeContext(watchedDirectories.ToImmutableArray()); + return new FileChangeContext([.. watchedDirectories]); } private class FileChangeContext : IFileChangeContext diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index b178281cb3bed..f68f4a7e7d536 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -185,7 +185,7 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList project Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); var languageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); - var unresolvedParams = new UnresolvedDependenciesParams(projectPaths.ToArray()); + var unresolvedParams = new UnresolvedDependenciesParams([.. projectPaths]); await languageServerManager.SendRequestAsync(ProjectNeedsRestoreName, unresolvedParams, cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs index 353aeb665de82..b6142d715962b 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs @@ -40,7 +40,7 @@ public async Task AddAdditionalFilesAsync(IReadOnlyList addition await using var _ = disposableBatchScope.ConfigureAwait(false); foreach (var additionalFile in additionalFiles) - _project.AddAdditionalFile(additionalFile.FilePath, folders: additionalFile.FolderNames.ToImmutableArray()); + _project.AddAdditionalFile(additionalFile.FilePath, folders: [.. additionalFile.FolderNames]); } public async Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) @@ -85,7 +85,7 @@ public async Task AddSourceFilesAsync(IReadOnlyList sourceFiles, await using var _ = disposableBatchScope.ConfigureAwait(false); foreach (var sourceFile in sourceFiles) - _project.AddSourceFile(sourceFile.FilePath, folders: sourceFile.FolderNames.ToImmutableArray()); + _project.AddSourceFile(sourceFile.FilePath, folders: [.. sourceFile.FolderNames]); } public void Dispose() @@ -190,7 +190,7 @@ public async Task SetCommandLineArgumentsAsync(IReadOnlyList arguments, var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); await using var _ = disposableBatchScope.ConfigureAwait(false); - _optionsProcessor.SetCommandLine(arguments.ToImmutableArray()); + _optionsProcessor.SetCommandLine([.. arguments]); } public async Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs index 9f91e4a0a2525..7f57a870a5cec 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs @@ -87,7 +87,7 @@ private static ImmutableArray GetRestorePaths(RestoreParams request, Sol { if (request.ProjectFilePaths.Any()) { - return request.ProjectFilePaths.ToImmutableArray(); + return [.. request.ProjectFilePaths]; } // No file paths were specified - this means we should restore all projects in the solution. diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs index 69df0964d0b8f..a166543886f52 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs @@ -52,7 +52,7 @@ public void HandleRawMessage(string rawMessage) public ImmutableArray GetTestCases() { Contract.ThrowIfFalse(_isComplete, "Tried to get test cases before discovery completed"); - return _testCases.ToImmutableArray(); + return [.. _testCases]; } public bool IsAborted() diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs index 1f8c71ce2d7ab..c3fb6f1dbc1db 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs @@ -45,6 +45,6 @@ public LanguageServerEndpointAttribute(string method) public LanguageServerEndpointAttribute(string method, string language, params string[] additionalLanguages) { Method = method; - Languages = new[] { language }.Concat(additionalLanguages).ToArray(); + Languages = [language, .. additionalLanguages]; } } diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 2b2b974ac5a7c..8eb7e16616167 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -98,7 +98,7 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) Range = true, Legend = new SemanticTokensLegend { - TokenTypes = SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes.ToArray(), + TokenTypes = [.. SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes], TokenModifiers = SemanticTokensSchema.TokenModifiers } }; diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index c6a06c30f422c..a06cee02e928e 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -390,7 +390,7 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text) else { var newText = await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText).ToImmutableArray(); + textChanges = [.. newText.GetTextChanges(oldText)]; } // Map all the text changes' spans for this document. diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs index f30b9ac390f0d..fde0bc07d9891 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs @@ -15,7 +15,7 @@ private class ProjectCodeFixProvider : AbstractProjectExtensionProvider { protected override ImmutableArray GetLanguages(ExportCodeFixProviderAttribute exportAttribute) - => exportAttribute.Languages.ToImmutableArray(); + => [.. exportAttribute.Languages]; protected override bool TryGetExtensionsFromReference(AnalyzerReference reference, out ImmutableArray extensions) { diff --git a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs index 00d5807883c73..e68ef976c26f6 100644 --- a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs +++ b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs @@ -64,7 +64,7 @@ public PEFile TryResolve(MetadataReference metadataReference, PEStreamOptions st { if (_inMemoryImagesForTesting.TryGetValue(metadataReference, out var pair)) { - return new PEFile(pair.fileName, new MemoryStream(pair.image.ToArray()), streamOptions); + return new PEFile(pair.fileName, new MemoryStream([.. pair.image]), streamOptions); } return null; diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index 17751bcd9e7d0..f829aa1582150 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -214,7 +214,7 @@ private static async Task a // order statesets // order will be in this order // BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers - OrderedStateSets = StateSetMap.Values.OrderBy(PriorityComparison).ToImmutableArray(); + OrderedStateSets = [.. StateSetMap.Values.OrderBy(PriorityComparison)]; } public HostAnalyzerStateSets WithExcludedAnalyzers(ImmutableHashSet excludedAnalyzers) diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 75513230f0080..0a05dd8864c9f 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -401,7 +401,7 @@ private static void AddUnifiedSuggestedActionsSet( sets.Add(new UnifiedSuggestedActionSet( originalSolution, category, - group.ToImmutableArray(), + [.. group], title: null, priority, applicableToSpan: groupKey.Item1.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text))); @@ -711,9 +711,7 @@ private static ImmutableArray GetInitiallyOrderedActi private static ImmutableArray OrderActionSets( ImmutableArray actionSets, TextSpan? selectionOpt) { - return actionSets.OrderByDescending(s => s.Priority) - .ThenBy(s => s, new UnifiedSuggestedActionSetComparer(selectionOpt)) - .ToImmutableArray(); + return [.. actionSets.OrderByDescending(s => s.Priority).ThenBy(s => s, new UnifiedSuggestedActionSetComparer(selectionOpt))]; } private static UnifiedSuggestedActionSet WithPriority( diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs index a0fa0af6eef3a..b33007e43a257 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -124,12 +124,12 @@ private static LSP.CodeAction[] GenerateCodeActions( { CommandIdentifier = CodeActionsHandler.RunNestedCodeActionCommandName, Title = title, - Arguments = [new CodeActionResolveData(title, codeAction.CustomTags, request.Range, request.TextDocument, codeActionPathList.ToArray(), fixAllFlavors: null, nestedCodeActions: nestedCodeActions)] + Arguments = [new CodeActionResolveData(title, codeAction.CustomTags, request.Range, request.TextDocument, [.. codeActionPathList], fixAllFlavors: null, nestedCodeActions: nestedCodeActions)] }; } AddLSPCodeActions(builder, codeAction, request, codeActionKind, diagnosticsForFix, nestedCodeActionCommand, - nestedCodeActions, codeActionPathList.ToArray(), suggestedAction); + nestedCodeActions, [.. codeActionPathList], suggestedAction); return builder.ToArray(); } @@ -161,7 +161,7 @@ private static LSP.CodeAction[] GenerateCodeActions( if (!isTopLevelCodeAction) { AddLSPCodeActions(nestedCodeActions, codeAction, request, codeActionKind, diagnosticsForFix, - nestedCodeActionCommand: null, nestedCodeActions: null, pathOfParentAction.ToArray(), suggestedAction); + nestedCodeActionCommand: null, nestedCodeActions: null, [.. pathOfParentAction], suggestedAction); } } @@ -240,7 +240,7 @@ private static VSInternalCodeAction GenerateVSCodeAction( Priority = UnifiedSuggestedActionSetPriorityToPriorityLevel(setPriority), Group = $"Roslyn{currentSetNumber}", ApplicableRange = applicableRange, - Data = new CodeActionResolveData(codeAction.Title, codeAction.CustomTags, request.Range, request.TextDocument, fixAllFlavors: null, nestedCodeActions: null, codeActionPath: codeActionPathList.ToArray()) + Data = new CodeActionResolveData(codeAction.Title, codeAction.CustomTags, request.Range, request.TextDocument, fixAllFlavors: null, nestedCodeActions: null, codeActionPath: [.. codeActionPathList]) }; static VSInternalCodeAction[] GenerateNestedVSCodeActions( diff --git a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs index 176c3f17b0739..806d070ff0463 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs @@ -53,14 +53,14 @@ public DocumentHighlightsHandler(IHighlightingService highlightingService, IGlob var keywordHighlights = await GetKeywordHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false); if (keywordHighlights.Any()) { - return keywordHighlights.ToArray(); + return [.. keywordHighlights]; } // Not a keyword, check if it is a reference that needs highlighting. var referenceHighlights = await GetReferenceHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false); if (referenceHighlights.Any()) { - return referenceHighlights.ToArray(); + return [.. referenceHighlights]; } // No keyword or references to highlight at this location. diff --git a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs index a488aefea3f12..7ea64d0c5d474 100644 --- a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -166,7 +166,7 @@ public OnAutoInsertHandler( var indentedText = GetIndentedText(newSourceText, caretLine, desiredCaretLinePosition, options); // Get the overall text changes between the original text and the formatted + indented text. - textChanges = indentedText.GetTextChanges(sourceText).ToImmutableArray(); + textChanges = [.. indentedText.GetTextChanges(sourceText)]; newSourceText = indentedText; // If tabs were inserted the desired caret column can remain beyond the line text. diff --git a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs index 1fd14087e596d..ee897994efe61 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs @@ -63,7 +63,7 @@ public GetTextDocumentWithContextHandler() return Task.FromResult(new VSProjectContextList { - ProjectContexts = contexts.ToArray(), + ProjectContexts = [.. contexts], DefaultIndex = documentIds.IndexOf(d => d == currentContextDocumentId) }); } diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 2338d5b383c31..9e57963e23ef3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -259,7 +259,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere var docText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var classifiedTextRuns = GetClassifiedTextRuns(_id, definitionId, documentSpan.Value, isWrittenTo, classifiedSpans, docText); - return new ClassifiedTextElement(classifiedTextRuns.ToArray()); + return new ClassifiedTextElement([.. classifiedTextRuns]); } // Certain definitions may not have a DocumentSpan, such as namespace and metadata definitions @@ -317,7 +317,7 @@ private static ClassifiedTextRun[] GetClassifiedTextRuns( private ValueTask ReportReferencesAsync(ImmutableSegmentedList> referencesToReport, CancellationToken cancellationToken) { // We can report outside of the lock here since _progress is thread-safe. - _progress.Report(referencesToReport.ToArray()); + _progress.Report([.. referencesToReport]); return ValueTaskFactory.CompletedTask; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs index 752ec12df2eeb..03c19b8338212 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs @@ -102,7 +102,7 @@ public SemanticTokensSchema(IReadOnlyDictionary tokenTypeMap) .Order() .ToImmutableArray(); - AllTokenTypes = SemanticTokenTypes.AllTypes.Concat(customTokenTypes).ToImmutableArray(); + AllTokenTypes = [.. SemanticTokenTypes.AllTypes, .. customTokenTypes]; var tokenTypeToIndex = new Dictionary(); diff --git a/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs b/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs index f604d0049d19f..099a38a5f3889 100644 --- a/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs +++ b/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs @@ -153,7 +153,7 @@ public void Dispose() ImmutableArray disposableServices; lock (_gate) { - disposableServices = _servicesToDispose.ToImmutableArray(); + disposableServices = [.. _servicesToDispose]; _servicesToDispose.Clear(); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs index 00dfa0293382c..b9d7c7a6dff16 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs @@ -390,7 +390,7 @@ void M() await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = clientCapability, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspMockCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ' ', '(')); @@ -437,7 +437,7 @@ public async Task TestUsingServerDefaultCommitCharacters(bool mutatingLspWorkspa var markup = "Item{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspMockCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ' ', '(')); @@ -765,7 +765,7 @@ public async Task TestSoftSelectionWhenFilterTextIsEmptyForPreselectItemAsync(bo var markup = "{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspMockCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithMatchPriority(MatchPriority.Preselect); @@ -873,7 +873,7 @@ public async Task TestHandleExceptionFromGetCompletionChange(bool mutatingLspWor var markup = "Item {|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspThrowExceptionOnChangeCompletionService; var builder = ImmutableArray.CreateBuilder(); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs index ed5bbf322c5e3..f4abf45dec3ce 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs @@ -181,7 +181,7 @@ private static LSP.DocumentSymbol CreateDocumentSymbol(LSP.SymbolKind kind, stri { var children = parent.Children.ToList(); children.Add(documentSymbol); - parent.Children = children.ToArray(); + parent.Children = [.. children]; } return documentSymbol; diff --git a/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs index a6c802e7ab9ee..3534e172218e2 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs @@ -110,7 +110,7 @@ public static void Main() Assert.False(reader.TryGetDocumentChecksum("/A/C.cs", out _, out _)); Assert.True(reader.TryGetDocumentChecksum("/a/c.cs", out var actualChecksum, out var actualAlgorithm)); - Assert.Equal("21-C8-B2-D7-A3-6B-49-C7-57-DF-67-B8-1F-75-DF-6A-64-FD-59-22", BitConverter.ToString(actualChecksum.ToArray())); + Assert.Equal("21-C8-B2-D7-A3-6B-49-C7-57-DF-67-B8-1F-75-DF-6A-64-FD-59-22", BitConverter.ToString([.. actualChecksum])); Assert.Equal(new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"), actualAlgorithm); } } diff --git a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs index 9685f82743816..e710e3b6e2c5b 100644 --- a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs +++ b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs @@ -100,7 +100,7 @@ internal static ImmutableArray CreateActiveStatementMap var activeStatementMarkers = SourceMarkers.GetActiveSpans(markedSource).ToArray(); var exceptionRegionMarkers = SourceMarkers.GetExceptionRegions(markedSource); - return activeStatementMarkers.Aggregate( + return [.. activeStatementMarkers.Aggregate( new List(), (list, marker) => { @@ -126,7 +126,7 @@ internal static ImmutableArray CreateActiveStatementMap documentActiveStatements.Add(unmappedActiveStatement.Statement); return SourceMarkers.SetListItem(list, ordinal, unmappedActiveStatement); - }).ToImmutableArray(); + })]; } internal static ImmutableArray GetUnmappedActiveStatements( diff --git a/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs b/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs index 0efea7cc9ab11..1c43208de1796 100644 --- a/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs +++ b/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs @@ -106,7 +106,7 @@ public static (int id, TextSpan span)[] GetTrackingSpans(string src) Contract.ThrowIfTrue(result.Any(span => span == default)); - return result.ToArray(); + return [.. result]; } public static ImmutableArray> GetExceptionRegions(string markedSource) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs index 8066dae54677e..0f901c2bb2478 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs @@ -44,7 +44,7 @@ public Data Serialize() => new Data() { HasGlobalAssemblyCache = HasGlobalAssemblyCache, - PlatformAssemblyPaths = PlatformAssemblyPaths.ToArray(), + PlatformAssemblyPaths = [.. PlatformAssemblyPaths], }; public static InteractiveHostPlatformInfo GetCurrentPlatformInfo() diff --git a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs index 1fd4f2c9b7e87..7b54f0437ba74 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs @@ -64,8 +64,8 @@ public Data Serialize() => new Data() { Success = Success, - SourcePaths = SourcePaths.ToArray(), - ReferencePaths = ReferencePaths.ToArray(), + SourcePaths = [.. SourcePaths], + ReferencePaths = [.. ReferencePaths], WorkingDirectory = WorkingDirectory, InitializationResult = InitializationResult?.Serialize(), }; diff --git a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs index 7dc06aaabe5b2..e1cac0b878d68 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs @@ -43,8 +43,8 @@ public Data Serialize() => new Data() { ScriptPath = ScriptPath, - MetadataReferencePaths = MetadataReferencePaths.ToArray(), - Imports = Imports.ToArray(), + MetadataReferencePaths = [.. MetadataReferencePaths], + Imports = [.. Imports], }; } } diff --git a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs index c236c73222d8f..ceaeb09bbb656 100644 --- a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs +++ b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs @@ -532,7 +532,7 @@ public async Task MissingRefrencesAutoResolution() var portableLibRef = portableLib.ToMetadataReference(); var loader = new InteractiveAssemblyLoader(); - loader.RegisterDependency(Assembly.Load(portableLib.EmitToArray().ToArray())); + loader.RegisterDependency(Assembly.Load([.. portableLib.EmitToArray()])); var s0 = await CSharpScript.Create("new C()", options: ScriptOptions.Default.AddReferences(portableLibRef), assemblyLoader: loader).RunAsync(); var c0 = s0.Script.GetCompilation(); @@ -569,7 +569,7 @@ public void HostObjectInInMemoryAssembly() var libImage = lib.EmitToArray(); var libRef = MetadataImageReference.CreateFromImage(libImage); - var libAssembly = Assembly.Load(libImage.ToArray()); + var libAssembly = Assembly.Load([.. libImage]); var globalsType = libAssembly.GetType("C"); var globals = Activator.CreateInstance(globalsType); diff --git a/src/Scripting/Core/Hosting/SynchronizedList.cs b/src/Scripting/Core/Hosting/SynchronizedList.cs index 1b99490d96b50..a6e399afb7a69 100644 --- a/src/Scripting/Core/Hosting/SynchronizedList.cs +++ b/src/Scripting/Core/Hosting/SynchronizedList.cs @@ -83,7 +83,7 @@ public IEnumerator GetEnumerator() lock (_guard) { // make a copy to ensure thread-safe enumeration - return ((IEnumerable)_list.ToArray()).GetEnumerator(); + return ((IEnumerable)[.. _list]).GetEnumerator(); } } diff --git a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs index f20f773ac3597..3cdb0a2d003c0 100644 --- a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs +++ b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs @@ -117,10 +117,10 @@ internal TSelf Verify() } readers.Add(generation.MetadataReader); - var verifier = new GenerationVerifier(index, generation, readers.ToImmutableArray()); + var verifier = new GenerationVerifier(index, generation, [.. readers]); generation.Verifier(verifier); - exceptions.Add(verifier.Exceptions.ToImmutableArray()); + exceptions.Add([.. verifier.Exceptions]); index++; } diff --git a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs index 8968f175f0b5a..d15171d69a9cd 100644 --- a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs +++ b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs @@ -72,7 +72,7 @@ public static ISymUnmanagedReader5 CreateReader(byte[] pdbImage, byte[] peImageO public static ISymUnmanagedReader5 CreateReader(ImmutableArray pdbImage, ImmutableArray peImageOpt = default(ImmutableArray)) { - return CreateReader(new MemoryStream(pdbImage.ToArray()), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); + return CreateReader(new MemoryStream([.. pdbImage]), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); } public static ISymUnmanagedReader5 CreateReader(Stream pdbStream, Stream peStreamOpt = null) diff --git a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs index 61de9bce50d17..f5dffe99106d3 100644 --- a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs +++ b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs @@ -1467,7 +1467,7 @@ private static void LoadDbiStream(BitAccess bits, out DbiModuleInfo[] modules, o if (modList.Count > 0) { - modules = modList.ToArray(); + modules = [.. modList]; } else { diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index d03b7331e2d4e..0adb386609d4f 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -493,11 +493,10 @@ static bool isCodeActionProviderType(Type t) => typeof(CodeFixProvider).IsAssign internal static ImmutableArray<(string TypeName, string Hash)> GetTelemetryInfos(ImmutableArray codeActionAndProviderTypes) { - return codeActionAndProviderTypes + return [.. codeActionAndProviderTypes .Distinct(FullNameTypeComparer.Instance) .Select(GetTelemetryInfo) - .OrderBy(info => info.TypeName) - .ToImmutableArray(); + .OrderBy(info => info.TypeName)]; static (string TypeName, string Hash) GetTelemetryInfo(Type type) { diff --git a/src/Tools/BuildValidator/CompilationDiff.cs b/src/Tools/BuildValidator/CompilationDiff.cs index a8d24aca794c2..ed741dbe60ccb 100644 --- a/src/Tools/BuildValidator/CompilationDiff.cs +++ b/src/Tools/BuildValidator/CompilationDiff.cs @@ -176,7 +176,7 @@ MetadataReader getRebuildPdbReader() { if (hasEmbeddedPdb) { - var peReader = new PEReader(rebuildBytes.ToImmutableArray()); + var peReader = new PEReader([.. rebuildBytes]); return peReader.GetEmbeddedPdbMetadataReader() ?? throw ExceptionUtilities.Unreachable(); } else @@ -261,8 +261,8 @@ void writeBinaryDiffArtifacts() Debug.Assert(_rebuildPdbReader is object); Debug.Assert(_rebuildCompilation is object); - var originalPeReader = new PEReader(_originalPortableExecutableBytes.ToImmutableArray()); - var rebuildPeReader = new PEReader(_rebuildPortableExecutableBytes.ToImmutableArray()); + var originalPeReader = new PEReader([.. _originalPortableExecutableBytes]); + var rebuildPeReader = new PEReader([.. _rebuildPortableExecutableBytes]); var originalInfo = new BuildInfo( AssemblyBytes: _originalPortableExecutableBytes, AssemblyReader: originalPeReader, @@ -401,7 +401,7 @@ void writeEmbeddedFileInfo() { if (!info.CompressedHash.IsDefaultOrEmpty) { - var hashString = BitConverter.ToString(info.CompressedHash.ToArray()).Replace("-", ""); + var hashString = BitConverter.ToString([.. info.CompressedHash]).Replace("-", ""); writer.WriteLine($@"\t""{Path.GetFileName(info.SourceTextInfo.OriginalSourceFilePath)}"" - {hashString}"); } } diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 7f19bb818826a..7b74aa2bb1ada 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -108,7 +108,7 @@ static int HandleCommand(string[] assembliesPath, string[]? exclude, string sour excludes.Add(Path.DirectorySeparatorChar + "runtimes" + Path.DirectorySeparatorChar); excludes.Add(@".resources.dll"); - var options = new Options(assembliesPath, referencesPath, excludes.ToArray(), sourcePath, verbose, quiet, debug, debugPath); + var options = new Options(assembliesPath, referencesPath, [.. excludes], sourcePath, verbose, quiet, debug, debugPath); // TODO: remove the DemoLoggerProvider or convert it to something more permanent var loggerFactory = LoggerFactory.Create(builder => @@ -221,7 +221,7 @@ private static AssemblyInfo[] GetAssemblyInfos( } } - return map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer).ToArray(); + return [.. map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer)]; static IEnumerable getAssemblyPaths(string directory) { diff --git a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs index d186da23ac528..64b1a31fdfa0d 100644 --- a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs @@ -65,7 +65,7 @@ public override async Task> MapSpansAsync( } } - return roslynSpans.ToImmutableArray(); + return [.. roslynSpans]; } } } diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs index 444b79364ae03..2467e612b5105 100644 --- a/src/Tools/Replay/Replay.cs +++ b/src/Tools/Replay/Replay.cs @@ -196,7 +196,7 @@ static async Task BuildAsync( var request = BuildServerConnection.CreateBuildRequest( outputName, compilerCall.IsCSharp ? RequestLanguage.CSharpCompile : RequestLanguage.VisualBasicCompile, - args.ToList(), + [.. args], workingDirectory: compilerCall.ProjectDirectory, tempDirectory: options.TempDirectory, keepAlive: null, diff --git a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs index 1bfd842325f35..8c18e4f662dc1 100644 --- a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs +++ b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs @@ -106,7 +106,7 @@ internal void ExecuteImpl(IEnumerable<(string apiSpecPath, IReadOnlyList } var peImageBuffer = File.ReadAllBytes(originalReferencePath); - Rewrite(peImageBuffer, patterns.ToImmutableArray()); + Rewrite(peImageBuffer, [.. patterns]); try { @@ -302,21 +302,21 @@ internal static unsafe void Rewrite(byte[] peImage, ImmutableArray p writer, metadataReader, patterns, - types.OrderBy(t => t.MetadataToken).ToImmutableArray(), + [.. types.OrderBy(t => t.MetadataToken)], metadataOffset); UpdateMethodDefinitions( writer, metadataReader, patterns, - methods.OrderBy(t => t.MetadataToken).ToImmutableArray(), + [.. methods.OrderBy(t => t.MetadataToken)], metadataOffset); UpdateFieldDefinitions( writer, metadataReader, patterns, - fields.OrderBy(t => t.MetadataToken).ToImmutableArray(), + [.. fields.OrderBy(t => t.MetadataToken)], metadataOffset); // unsign: diff --git a/src/Tools/Source/RunTests/AssemblyScheduler.cs b/src/Tools/Source/RunTests/AssemblyScheduler.cs index 4f54d609f98a8..56eb6bb308f1f 100644 --- a/src/Tools/Source/RunTests/AssemblyScheduler.cs +++ b/src/Tools/Source/RunTests/AssemblyScheduler.cs @@ -112,7 +112,7 @@ static ImmutableArray CreateWorkItemsForFullAssemblies(ImmutableAr workItems.Add(new WorkItemInfo(currentWorkItem, partitionIndex++)); } - return workItems.ToImmutableArray(); + return [.. workItems]; } } @@ -235,7 +235,7 @@ private ImmutableArray BuildWorkItems( // Add any remaining tests to the work item. AddCurrentWorkItem(); - return workItems.ToImmutableArray(); + return [.. workItems]; void AddCurrentWorkItem() { diff --git a/src/Tools/Source/RunTests/Program.cs b/src/Tools/Source/RunTests/Program.cs index 104bdcad356bb..121cd0dc85dc9 100644 --- a/src/Tools/Source/RunTests/Program.cs +++ b/src/Tools/Source/RunTests/Program.cs @@ -351,7 +351,7 @@ private static ImmutableArray GetAssemblyFilePaths(Options options } list.Sort(); - return list.ToImmutableArray(); + return [.. list]; static bool shouldInclude(string name, Options options) { diff --git a/src/Tools/Source/RunTests/TestRunner.cs b/src/Tools/Source/RunTests/TestRunner.cs index f837f34a2e8d9..2d21960723ac5 100644 --- a/src/Tools/Source/RunTests/TestRunner.cs +++ b/src/Tools/Source/RunTests/TestRunner.cs @@ -406,7 +406,7 @@ internal async Task RunAllAsync(ImmutableArray workI processResults.AddRange(c.ProcessResults); } - return new RunAllResult((failures == 0), completed.ToImmutableArray(), processResults.ToImmutable()); + return new RunAllResult((failures == 0), [.. completed], processResults.ToImmutable()); } private void Print(List testResults) diff --git a/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs b/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs index 450ff38f04e2f..1453ad36fe7dc 100644 --- a/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs +++ b/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs @@ -47,7 +47,7 @@ public override SymbolDisplayPart[] GeneratePreviewDisplayParts(AddedParameterVi parts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Text, null, addedParameterViewModel.Default)); } - return parts.ToArray(); + return [.. parts]; } // Use LangVersion Preview to ensure that all types parse correctly. If the user types in a type only available diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs index 6544889756b73..c5481121e2963 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs @@ -66,7 +66,7 @@ public SettingsEditorControl(ISettingsEditorView whitespaceView, analyzerView ]; - _tableControls = _views.SelectAsArray(view => view.TableControl).ToArray(); + _tableControls = [.. _views.SelectAsArray(view => view.TableControl)]; InitializeComponent(); } diff --git a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs index 9e5a817ba411b..49794d90e20f2 100644 --- a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs +++ b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs @@ -75,7 +75,7 @@ public void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureNam closeAfterAction: true)); } - ShowGlobalErrorInfo(message, featureName, exception, infoBarUIs.ToArray()); + ShowGlobalErrorInfo(message, featureName, exception, [.. infoBarUIs]); } private void LogGlobalErrorToActivityLog(string message, string? detailedError) diff --git a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs index 2cc9eb4b5c06b..a2a015311f028 100644 --- a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs @@ -46,7 +46,7 @@ public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable GetPartialForNamedTypeAsync(INamedTypeSym partials.Add(GraphNodeId.GetArray( CodeGraphNodeIdName.GenericArgumentsIdentifier, - genericArguments.ToArray())); + [.. genericArguments])); } if (namedType.ContainingType != null) @@ -206,7 +206,7 @@ private static async Task GetPartialForNamedTypeAsync(INamedTypeSym partials.Add(await GetPartialForTypeAsync(namedType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken, hasGenericArguments).ConfigureAwait(false)); } - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } } @@ -231,7 +231,7 @@ private static async Task GetPartialForPointerTypeAsync(IPointerTyp partials.Add(await GetPartialForTypeAsync(pointerType.PointedAtType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); } - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } private static async Task GetPartialForArrayTypeAsync(IArrayTypeSymbol arrayType, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) @@ -252,7 +252,7 @@ private static async Task GetPartialForArrayTypeAsync(IArrayTypeSym partials.Add(GraphNodeId.GetPartial(CodeQualifiedName.ArrayRank, arrayType.Rank.ToString())); partials.Add(await GetPartialForTypeAsync(arrayType.ElementType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } private static async Task GetPartialForTypeParameterSymbolAsync(ITypeParameterSymbol typeParameterSymbol, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) @@ -317,7 +317,7 @@ public static async Task GetIdForMemberAsync(ISymbol member, Soluti nodes.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, ParamKind.Ref)); } - parameterTypeIds.Add(GraphNodeId.GetNested(nodes.ToArray())); + parameterTypeIds.Add(GraphNodeId.GetNested([.. nodes])); } if (member is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.Conversion) @@ -336,25 +336,25 @@ public static async Task GetIdForMemberAsync(ISymbol member, Soluti var returnTypePartial = nodes.ToList(); returnTypePartial.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, Microsoft.VisualStudio.GraphModel.CodeSchema.ParamKind.Return)); - var returnCollection = GraphNodeId.GetNested(returnTypePartial.ToArray()); + var returnCollection = GraphNodeId.GetNested([.. returnTypePartial]); parameterTypeIds.Add(returnCollection); } memberPartials.Add(GraphNodeId.GetArray( CodeGraphNodeIdName.OverloadingParameters, - parameterTypeIds.ToArray())); + [.. parameterTypeIds])); } partials.Add(GraphNodeId.GetPartial( CodeGraphNodeIdName.Member, - MakeCollectionIfNecessary(memberPartials.ToArray()))); + MakeCollectionIfNecessary([.. memberPartials]))); } else { partials.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Member, member.MetadataName)); } - return GraphNodeId.GetNested(partials.ToArray()); + return GraphNodeId.GetNested([.. partials]); } private static object MakeCollectionIfNecessary(GraphNodeId[] array) diff --git a/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs b/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs index ace4d7cfc5ea6..e15e7d56ecbc2 100644 --- a/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs +++ b/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs @@ -40,7 +40,7 @@ public IWpfTableControl4 CreateTableControl() _tableManager, autoSubscribe: true, BuildColumnStates(), - UnusedReferencesColumnDefinitions.ColumnNames.ToArray()); + [.. UnusedReferencesColumnDefinitions.ColumnNames]); tableControl.ShowGroupingLine = true; tableControl.DoColumnsAutoAdjust = true; tableControl.DoSortingAndGroupingWhileUnstable = true; diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs index 3a1213d0d2013..4a980f4e05115 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs @@ -141,7 +141,7 @@ private static object[] GetValidArray(object itemOrArray, bool allowMultipleElem } } - return result.ToArray(); + return [.. result]; } internal EnvDTE80.CodeAttributeArgument AddAttributeArgument(SyntaxNode containerNode, string name, string value, object position) diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs index 9ca3dba4d904b..94ff8208e4117 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs @@ -148,7 +148,7 @@ public void UpdatePreview(string text) _editorOptions.CreateOptions(), textBuffer.CurrentSnapshot, separator: "", - exposedLineSpans: GetExposedLineSpans(textBuffer.CurrentSnapshot).ToArray()); + exposedLineSpans: [.. GetExposedLineSpans(textBuffer.CurrentSnapshot)]); var textView = _textEditorFactoryService.CreateTextView(projection, _textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Interactive)); @@ -224,7 +224,7 @@ protected void AddParenthesesOption( isChecked: !defaultAddForClarity)); CodeStyleItems.Add(new EnumCodeStyleOptionViewModel( - languageOption, title, preferences.ToArray(), + languageOption, title, [.. preferences], examples, this, optionStore, ServicesVSResources.Parentheses_preferences_colon, codeStylePreferences)); } diff --git a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs index 983d1474bfea5..75f2401bcaaf0 100644 --- a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs +++ b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs @@ -72,7 +72,7 @@ private void AddButton_Click(object sender, RoutedEventArgs e) private void ManageSpecificationsButton_Click(object sender, RoutedEventArgs e) { - var viewModel = new ManageSymbolSpecificationsDialogViewModel(_viewModel.Specifications, _viewModel.CodeStyleItems.ToList(), _languageName, _notificationService); + var viewModel = new ManageSymbolSpecificationsDialogViewModel(_viewModel.Specifications, [.. _viewModel.CodeStyleItems], _languageName, _notificationService); var dialog = new ManageNamingStylesInfoDialog(viewModel); if (dialog.ShowModal().Value == true) { @@ -82,7 +82,7 @@ private void ManageSpecificationsButton_Click(object sender, RoutedEventArgs e) private void ManageStylesButton_Click(object sender, RoutedEventArgs e) { - var viewModel = new ManageNamingStylesDialogViewModel(_viewModel.NamingStyles, _viewModel.CodeStyleItems.ToList(), _notificationService); + var viewModel = new ManageNamingStylesDialogViewModel(_viewModel.NamingStyles, [.. _viewModel.CodeStyleItems], _notificationService); var dialog = new ManageNamingStylesInfoDialog(viewModel); if (dialog.ShowModal().Value == true) { diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 45fdb1c714ec7..50107b9f10764 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -1525,7 +1525,7 @@ private static Solution Populate(Solution solution) ], [ "cs additional file content" - ], solution.ProjectIds.ToArray()); + ], [.. solution.ProjectIds]); solution = AddProject(solution, LanguageNames.CSharp, [ diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs index 8af34dd060cae..d3da6ef64f484 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs @@ -309,7 +309,7 @@ public async Task GetLightBulbPreviewClassificationsAsync( activeSession.Collapse(); var classifier = classifierAggregatorService.GetClassifier(preview); var classifiedSpans = classifier.GetClassificationSpans(new SnapshotSpan(preview.TextBuffer.CurrentSnapshot, 0, preview.TextBuffer.CurrentSnapshot.Length)); - return classifiedSpans.ToArray(); + return [.. classifiedSpans]; } activeSession.Collapse(); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs index 517c21d606ad1..c9ca70e510dc0 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs @@ -157,7 +157,7 @@ static ScreenshotInProcess() return; } - frames = s_frames.ToArray(); + frames = [.. s_frames]; } // Make sure the frames are processed in order of their timestamps @@ -303,7 +303,7 @@ private static (TimeSpan elapsed, BitmapSource image, Size offset)[] DetectChang Marshal.FreeHGlobal(imageBuffer); } - return resultFrames.ToArray(); + return [.. resultFrames]; } private static void WritePngSignature(Stream stream, byte[] buffer) diff --git a/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs b/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs index e922257ddd994..38c1fb7179dc0 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs @@ -23,7 +23,7 @@ public static string GetLanguageServerProviderServiceName(string[] contentTypes) public static string GetLanguageServerProviderServiceName(string lspServiceName) => LanguageServerProviderServiceName + "-" + lspServiceName; - public static string GetContentTypesName(string[] contentTypes) => string.Join("-", contentTypes.OrderBy(c => c).ToArray()); + public static string GetContentTypesName(string[] contentTypes) => string.Join("-", [.. contentTypes.OrderBy(c => c)]); public static bool IsContentTypeRemote(string contentType) => contentType.EndsWith("-remote"); diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs index 38fd06245c515..e949ef523ae1c 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs @@ -99,7 +99,7 @@ async Task CompareDocumentAsync(Document document) { lock (gate) { - output.AppendLine($"{document.FilePath}: {BitConverter.ToString(snapshotChecksum.ToArray())} : {BitConverter.ToString(fileChecksum.ToArray())}"); + output.AppendLine($"{document.FilePath}: {BitConverter.ToString([.. snapshotChecksum])} : {BitConverter.ToString([.. fileChecksum])}"); outOfDateCount++; } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs index e7c14eb07a3eb..a1ee575ad5aed 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs @@ -55,13 +55,13 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe var document = context.Document; if (document == null) { - return locations.ToArray(); + return [.. locations]; } var xamlGoToDefinitionService = document.Project.Services.GetService(); if (xamlGoToDefinitionService == null) { - return locations.ToArray(); + return [.. locations]; } var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); @@ -83,7 +83,7 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe await Task.WhenAll(tasks).ConfigureAwait(false); - return locations.ToArray(); + return [.. locations]; } private async Task GetLocationsAsync(XamlDefinition definition, RequestContext context, CancellationToken cancellationToken) diff --git a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs index c45a418d8c073..5df82749fae48 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs @@ -148,7 +148,7 @@ public ImmutableArray GetFormattingChangesOnTypedCharacter( return changes; } - return FormatToken(document, indentationOptions, token, formattingRules, cancellationToken).ToImmutableArray(); + return [.. FormatToken(document, indentationOptions, token, formattingRules, cancellationToken)]; } private static bool OnlySmartIndentCloseBrace(in AutoFormattingOptions options) @@ -199,7 +199,7 @@ private static ImmutableArray FormatRange( var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)document.Root, document.Text); var changes = formatter.FormatRange(tokenRange.Value.Item1, tokenRange.Value.Item2, cancellationToken); - return changes.ToImmutableArray(); + return [.. changes]; } private static IEnumerable GetTypingRules(SyntaxToken tokenBeforeCaret) @@ -318,9 +318,12 @@ or SyntaxKind.EndOfDirectiveToken private ImmutableArray GetFormattingRules(ParsedDocument document, int position, SyntaxToken tokenBeforeCaret) { var formattingRuleFactory = _services.SolutionServices.GetRequiredService(); - return ImmutableArray.Create(formattingRuleFactory.CreateRule(document, position)) - .AddRange(GetTypingRules(tokenBeforeCaret)) - .AddRange(Formatter.GetDefaultFormattingRules(_services)); + return + [ + formattingRuleFactory.CreateRule(document, position), + .. GetTypingRules(tokenBeforeCaret), + .. Formatter.GetDefaultFormattingRules(_services), + ]; } public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken) @@ -332,7 +335,7 @@ public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument doc rules.AddRange(service.GetDefaultFormattingRules()); var result = service.GetFormattingResult(document.Root, [formattingSpan], options, rules, cancellationToken); - return result.GetTextChanges(cancellationToken).ToImmutableArray(); + return [.. result.GetTextChanges(cancellationToken)]; } internal sealed class PasteFormattingRule : AbstractFormattingRule diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs index 127cfd73e6a56..55bdb955984c6 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs @@ -31,7 +31,7 @@ public static ImmutableArray Compute(SyntaxNode root, Func< { var reduceNodeComputer = new NodesAndTokensToReduceComputer(isNodeOrTokenOutsideSimplifySpans); reduceNodeComputer.Visit(root); - return reduceNodeComputer._nodesAndTokensToReduce.ToImmutableArray(); + return [.. reduceNodeComputer._nodesAndTokensToReduce]; } private NodesAndTokensToReduceComputer(Func isNodeOrTokenOutsideSimplifySpans) diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs index 9a331882f074f..0cba06c47234c 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs @@ -260,7 +260,7 @@ public void EndBatchBuild() _batchBuildLogger?.SetProjectAndLog(projectInstance.FullPath, log); - var buildRequestData = new MSB.Execution.BuildRequestData(projectInstance, targets.ToArray()); + var buildRequestData = new MSB.Execution.BuildRequestData(projectInstance, [.. targets]); var result = await BuildAsync(buildRequestData, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs index ef9950a71ca1b..0dcc41acbffff 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs @@ -48,7 +48,7 @@ public static ImmutableArray GetPackageReferences(this MSB.Exe references.Add(packageReference); } - return references.ToImmutableArray(); + return [.. references]; } /// diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 53deba14cf6e3..e358758782d98 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -39,7 +39,7 @@ protected ProjectFile(ProjectFileLoader loader, MSB.Evaluation.Project? loadedPr Log = log; } - public ImmutableArray GetDiagnosticLogItems() => Log.ToImmutableArray(); + public ImmutableArray GetDiagnosticLogItems() => [.. Log]; protected abstract SourceCodeKind GetSourceCodeKind(string documentFileName); public abstract string GetDocumentExtension(SourceCodeKind kind); @@ -252,7 +252,7 @@ private ImmutableArray GetRelativeFolders(MSB.Framework.ITaskItem docume var linkPath = documentItem.GetMetadata(MetadataNames.Link); if (!RoslynString.IsNullOrEmpty(linkPath)) { - return PathUtilities.GetDirectoryName(linkPath).Split(PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar).ToImmutableArray(); + return [.. PathUtilities.GetDirectoryName(linkPath).Split(PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar)]; } else { diff --git a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs index e50bf7d57852b..0bdbcd428bba0 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs @@ -149,7 +149,7 @@ public async ValueTask DisposeAsync() // may try to mutate the list while we're enumerating. using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) { - processesToDispose = _processes.Values.ToList(); + processesToDispose = [.. _processes.Values]; _processes.Clear(); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index d22ddc0d0a33e..c5c2b09fd0eb2 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -457,7 +457,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) var fileName = Path.ChangeExtension(info.Name, extension); var relativePath = (info.Folders != null && info.Folders.Count > 0) - ? Path.Combine(Path.Combine(info.Folders.ToArray()), fileName) + ? Path.Combine(Path.Combine([.. info.Folders]), fileName) : fileName; var fullPath = GetAbsolutePath(relativePath, Path.GetDirectoryName(project.FilePath)!); diff --git a/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs b/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs index f8178c6c5933f..a40000b3ff50b 100644 --- a/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs +++ b/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs @@ -52,7 +52,7 @@ private SyntaxNode CaseCorrect(SemanticModel? semanticModel, SyntaxNode root, Im using (Logger.LogBlock(FunctionId.CaseCorrection_AddReplacements, cancellationToken)) { - AddReplacements(semanticModel, root, normalizedSpanCollection.ToImmutableArray(), replacements, cancellationToken); + AddReplacements(semanticModel, root, [.. normalizedSpanCollection], replacements, cancellationToken); } using (Logger.LogBlock(FunctionId.CaseCorrection_ReplaceTokens, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs index 1d45eb4e6ab7b..dd8e71e8f7906 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs @@ -139,7 +139,7 @@ private static ImmutableArray MergeClassifiedSpans( // be gaps in what it produces. Fill in those gaps so we have *all* parts of the span classified properly. using var _2 = Classifier.GetPooledList(out var filledInSpans); FillInClassifiedSpanGaps(widenedSpan.Start, mergedSpans, filledInSpans); - return filledInSpans.ToImmutableArray(); + return [.. filledInSpans]; } private static readonly Comparison s_spanComparison = static (s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start; diff --git a/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs b/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs index a7bec9d9175e8..2a65deb55737d 100644 --- a/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs +++ b/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs @@ -530,7 +530,7 @@ private ImmutableArray GetSpans( // Remove the spans we should not touch from the requested spans and return that final set. var result = NormalizedTextSpanCollection.Difference(requestedSpans, spansToAvoid); - return result.ToImmutableArray(); + return [.. result]; } private async Task IterateAllCodeCleanupProvidersAsync( @@ -594,7 +594,7 @@ private string GetCodeCleanerTypeName(ICodeCleanupProvider codeCleaner) private static SyntaxNode InjectAnnotations(SyntaxNode node, Dictionary> map) { var tokenMap = map.ToDictionary(p => p.Key, p => p.Value); - return node.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o].ToArray())); + return node.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations([.. tokenMap[o]])); } private static bool TryCreateTextSpan(int start, int end, out TextSpan span) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs index 680b935e03483..883c28be7be1a 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs @@ -30,11 +30,11 @@ internal struct DiagnosticAnalysisResultBuilder(Project project, VersionStamp ve private List? _lazyOthers = null; - public readonly ImmutableHashSet DocumentIds => _lazyDocumentsWithDiagnostics == null ? [] : _lazyDocumentsWithDiagnostics.ToImmutableHashSet(); + public readonly ImmutableHashSet DocumentIds => _lazyDocumentsWithDiagnostics == null ? [] : [.. _lazyDocumentsWithDiagnostics]; public readonly ImmutableDictionary> SyntaxLocals => Convert(_lazySyntaxLocals); public readonly ImmutableDictionary> SemanticLocals => Convert(_lazySemanticLocals); public readonly ImmutableDictionary> NonLocals => Convert(_lazyNonLocals); - public readonly ImmutableArray Others => _lazyOthers == null ? [] : _lazyOthers.ToImmutableArray(); + public readonly ImmutableArray Others => _lazyOthers == null ? [] : [.. _lazyOthers]; public void AddExternalSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) { diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 942a846fa8671..02950b6b50ef3 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -404,7 +404,7 @@ private static async Task> GetPragmaSuppressionAnalyz } await Task.WhenAll(tasks).ConfigureAwait(false); - return bag.ToImmutableArray(); + return [.. bag]; } else { diff --git a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs index 555dfa7b6826f..6615ecbd563fe 100644 --- a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs +++ b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs @@ -229,7 +229,7 @@ private async Task AddImportDirectivesFromSymbolAnnotationsAsync( var importContainer = addImportsService.GetImportContainer(root, context, importToSyntax.First().Value, options); // Now remove any imports we think can cause conflicts in that container. - var safeImportsToAdd = GetSafeToAddImports(importToSyntax.Keys.ToImmutableArray(), importContainer, model, cancellationToken); + var safeImportsToAdd = GetSafeToAddImports([.. importToSyntax.Keys], importContainer, model, cancellationToken); var importsToAdd = importToSyntax.Where(kvp => safeImportsToAdd.Contains(kvp.Key)).Select(kvp => kvp.Value).ToImmutableArray(); if (importsToAdd.Length == 0) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index b2a14157d4adc..84aeec57d7ada 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -35,7 +35,7 @@ public static async Task> GetDependentProjectsAsync( { // namespaces are visible in all projects. if (symbols.Any(static s => s.Kind == SymbolKind.Namespace)) - return projects.ToImmutableArray(); + return [.. projects]; var dependentProjects = await GetDependentProjectsWorkerAsync(solution, symbols, cancellationToken).ConfigureAwait(false); return dependentProjects.WhereAsArray(projects.Contains); @@ -84,7 +84,7 @@ private static async Task> GetDependentProjectsWorkerAsy result.AddRange(filteredProjects.Select(p => p.project)); } - return result.ToImmutableArray(); + return [.. result]; } /// @@ -145,7 +145,7 @@ private static async Task> GetDependentProjectsWorkerAsy // further submissions can bind to them. await AddSubmissionDependentProjectsAsync(solution, symbolOrigination.sourceProject, dependentProjects, cancellationToken).ConfigureAwait(false); - return dependentProjects.ToImmutableArray(); + return [.. dependentProjects]; } private static async Task AddSubmissionDependentProjectsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 79f7ef5d7893a..9e360bd3f47cb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -136,7 +136,7 @@ private static async Task> DescendInheritanceTr await DescendInheritanceTreeInProjectAsync(project).ConfigureAwait(false); } - return result.ToImmutableArray(); + return [.. result]; async Task DescendInheritanceTreeInProjectAsync(Project project) { @@ -472,7 +472,7 @@ private static ImmutableArray OrderTopologically( index++; } - return projectsToExamine.OrderBy((p1, p2) => order[p1.Id] - order[p2.Id]).ToImmutableArray(); + return [.. projectsToExamine.OrderBy((p1, p2) => order[p1.Id] - order[p2.Id])]; } private static ImmutableArray GetProjectsToExamineWorker( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs index a8ae4b1571f97..66379d0db9d32 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs @@ -42,7 +42,7 @@ public BidirectionalSymbolSet( } public override ImmutableArray GetAllSymbols() - => _allSymbols.ToImmutableArray(); + => [.. _allSymbols]; public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs index a6778eb0608a7..2463a1d7033dc 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs @@ -16,7 +16,7 @@ internal partial class FindReferencesSearchEngine /// private sealed class NonCascadingSymbolSet(FindReferencesSearchEngine engine, MetadataUnifyingSymbolHashSet searchSymbols) : SymbolSet(engine) { - private readonly ImmutableArray _symbols = searchSymbols.ToImmutableArray(); + private readonly ImmutableArray _symbols = [.. searchSymbols]; public override ImmutableArray GetAllSymbols() => _symbols; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs index 2e8ac46d238ac..f225f63350c2d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs @@ -40,7 +40,7 @@ public override ImmutableArray GetAllSymbols() var result = new MetadataUnifyingSymbolHashSet(); result.AddRange(_upSymbols); result.AddRange(initialSymbols); - return result.ToImmutableArray(); + return [.. result]; } public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 7383f6e71739a..2b74a456075d8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -93,7 +93,7 @@ protected static async Task> FindDocumentsAsync( { var document = scope.First(); if (document.Project == project) - return scope.ToImmutableArray(); + return [.. scope]; return []; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index dfc42e1bf5983..a8e642c4330c7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -42,7 +42,7 @@ public ImmutableArray GetReferencedSymbols() { using var _ = ArrayBuilder.GetInstance(out var result); foreach (var (symbol, locations) in _symbolToLocations) - result.Add(new ReferencedSymbol(symbol, locations.ToImmutableArray())); + result.Add(new ReferencedSymbol(symbol, [.. locations])); return result.ToImmutable(); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 89be7cdadb127..50b7dd3af8c58 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -67,7 +67,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated map[symbolAndProjectId] = symbol; } - var symbolGroup = new SymbolGroup(map.Values.ToImmutableArray()); + var symbolGroup = new SymbolGroup([.. map.Values]); lock (_gate) { _groupMap[dehydrated] = symbolGroup; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index f69e50e9a3529..89ac7f1794849 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs @@ -308,6 +308,6 @@ internal static async Task> FindLinkedSymbolsAsync( } } - return linkedSymbols.ToImmutableArray(); + return [.. linkedSymbols]; } } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index 6603464b13a8e..1f7fb23a853f3 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -83,7 +83,7 @@ private async Task MergeLinkedDocumentGroupAsync( appliedChanges = await AddDocumentMergeChangesAsync( oldSolution.GetDocument(documentId), newSolution.GetDocument(documentId), - appliedChanges.ToList(), + [.. appliedChanges], unmergedChanges, groupSessionInfo, textDifferencingService, diff --git a/src/Workspaces/Core/Portable/Log/AggregateLogger.cs b/src/Workspaces/Core/Portable/Log/AggregateLogger.cs index 19a74f1a4972d..6f20609433576 100644 --- a/src/Workspaces/Core/Portable/Log/AggregateLogger.cs +++ b/src/Workspaces/Core/Portable/Log/AggregateLogger.cs @@ -36,7 +36,7 @@ public static AggregateLogger Create(params ILogger[] loggers) set.Add(logger); } - return new AggregateLogger(set.ToImmutableArray()); + return new AggregateLogger([.. set]); } public static ILogger AddOrReplace(ILogger newLogger, ILogger oldLogger, Func predicate) @@ -81,7 +81,7 @@ public static ILogger AddOrReplace(ILogger newLogger, ILogger oldLogger, Func predicate) @@ -105,7 +105,7 @@ public static ILogger Remove(ILogger logger, Func predicate) return set.Single(); } - return new AggregateLogger(set.ToImmutableArray()); + return new AggregateLogger([.. set]); } private AggregateLogger(ImmutableArray loggers) diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index 34190d5261e2c..0a0b5e3c63411 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -177,10 +177,13 @@ public async Task ResolveConflictsAsync() if (phase == 1) { - conflictLocations = conflictLocations.Concat(conflictResolution.RelatedLocations - .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) - .Select(loc => new ConflictLocationInfo(loc))) - .ToImmutableHashSet(); + conflictLocations = + [ + .. conflictLocations, + .. conflictResolution.RelatedLocations + .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) + .Select(loc => new ConflictLocationInfo(loc)), + ]; } // Set the documents with conflicts that need to be processed in the next phase. diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs index 6c6546e908b97..7a71e5dd2b9a3 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs @@ -226,7 +226,7 @@ internal async Task SimplifyAsync(Solution solution, IEnumerable>(); foreach (var (docId, spans) in _documentToModifiedSpansMap) - builder.Add(docId, spans.ToImmutableArray()); + builder.Add(docId, [.. spans]); return builder.ToImmutable(); } @@ -238,7 +238,7 @@ public ImmutableDictionary> GetDocu foreach (var (docId, spans) in _documentToComplexifiedSpansMap) { builder.Add(docId, spans.SelectAsArray( - s => new ComplexifiedSpan(s.OriginalSpan, s.NewSpan, s.ModifiedSubSpans.ToImmutableArray()))); + s => new ComplexifiedSpan(s.OriginalSpan, s.NewSpan, [.. s.ModifiedSubSpans]))); } return builder.ToImmutable(); diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index 741bab2bd9cfa..cc3c90299437b 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -119,7 +119,7 @@ internal static async Task RenameDocumentAsync( if (document.Services.GetService() != null) { // Don't advertise that we can file rename generated documents that map to a different file. - return new RenameDocumentActionSet([], document.Id, document.Name, document.Folders.ToImmutableArray(), options); + return new RenameDocumentActionSet([], document.Id, document.Name, [.. document.Folders], options); } using var _ = ArrayBuilder.GetInstance(out var actions); @@ -143,7 +143,7 @@ internal static async Task RenameDocumentAsync( actions.ToImmutable(), document.Id, newDocumentName, - newDocumentFolders.ToImmutableArray(), + [.. newDocumentFolders], options); } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs index bf841432cddf8..aba239ec1cb92 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs @@ -344,7 +344,7 @@ private static XNode[] RewriteMany(ISymbol symbol, HashSet? visitedSymb result.AddRange(RewriteInheritdocElements(symbol, visitedSymbols, compilation, child, cancellationToken)); } - return result.ToArray(); + return [.. result]; } private static XNode[]? RewriteInheritdocElement(ISymbol memberSymbol, HashSet? visitedSymbols, Compilation compilation, XElement element, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs index b45c69cfd0dc4..f929abc39b74d 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs @@ -47,7 +47,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) if (arguments.SequenceEqual(symbol.TypeArguments)) return symbol; - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs index 24f728ff9b703..b591d1c9d2818 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs @@ -45,7 +45,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) return symbol; } - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs index 31510f8004fae..93bde82d583ef 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs @@ -49,7 +49,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) return symbol; } - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs index f9de85f7493c1..174d252614bbc 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs @@ -235,7 +235,7 @@ public ImmutableArray ActiveDiagnosticTokens return []; } - return _diagnosticTokenList.ToImmutableArray(); + return [.. _diagnosticTokenList]; } } } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs index 2fb062340a4c3..ed5be74296ee3 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs @@ -151,7 +151,7 @@ public async Task WaitAllAsync(Workspace? workspace, string[]? featureNames = nu do { // wait for all current tasks to be done for the time given - if (Task.WaitAll(tasks.ToArray(), smallTimeout)) + if (Task.WaitAll([.. tasks], smallTimeout)) { // current set of tasks are done. // see whether there are new tasks added while we were waiting diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs index e73c12d226a0f..823d3ae7e5cca 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs @@ -37,7 +37,7 @@ internal sealed class DefaultPersistentStorageConfiguration : IPersistentStorage /// path. For example, Base64 encoding will use / which is something that we definitely do not want /// errantly added to a path. /// - private static readonly ImmutableArray s_invalidPathChars = Path.GetInvalidPathChars().Concat('/').ToImmutableArray(); + private static readonly ImmutableArray s_invalidPathChars = [.. Path.GetInvalidPathChars(), '/']; private static readonly string s_cacheDirectory; private static readonly string s_moduleFileName; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs index 04ba30d41afd4..0f6ed5627c13a 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs @@ -557,7 +557,7 @@ internal void UpdateSolutionForBatch( ClearAndZeroCapacity(_documentsAddedInBatch); // Document removing... - solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, _documentsRemovedInBatch.ToImmutableArray()), + solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, [.. _documentsRemovedInBatch]), removeDocumentChangeKind, _documentsRemovedInBatch); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs index 80fcf4203170b..a6b39decd2bb0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs @@ -270,7 +270,7 @@ private ImmutableHashSet GetProjectsThatThisProjectTransitivelyDepend using var pooledObject = SharedPools.Default>().GetPooledObject(); var results = pooledObject.Object; this.ComputeTransitiveReferences(projectId, results); - transitiveReferences = results.ToImmutableHashSet(); + transitiveReferences = [.. results]; _transitiveReferencesMap = _transitiveReferencesMap.Add(projectId, transitiveReferences); } @@ -323,7 +323,7 @@ private ImmutableHashSet GetProjectsThatTransitivelyDependOnThisProje var results = pooledObject.Object; ComputeReverseTransitiveReferences(projectId, results); - reverseTransitiveReferences = results.ToImmutableHashSet(); + reverseTransitiveReferences = [.. results]; _reverseTransitiveReferencesMap = _reverseTransitiveReferencesMap.Add(projectId, reverseTransitiveReferences); } @@ -367,7 +367,7 @@ private void GetTopologicallySortedProjects_NoLock(CancellationToken cancellatio using var seenProjects = SharedPools.Default>().GetPooledObject(); using var resultList = SharedPools.Default>().GetPooledObject(); this.TopologicalSort(_projectIds, seenProjects.Object, resultList.Object, cancellationToken); - _lazyTopologicallySortedProjects = resultList.Object.ToImmutableArray(); + _lazyTopologicallySortedProjects = [.. resultList.Object]; } } @@ -419,7 +419,7 @@ private ImmutableArray> GetDependencySets_NoLock(Cancella using var seenProjects = SharedPools.Default>().GetPooledObject(); using var results = SharedPools.Default>>().GetPooledObject(); this.ComputeDependencySets(seenProjects.Object, results.Object, cancellationToken); - _lazyDependencySets = results.Object.ToImmutableArray(); + _lazyDependencySets = [.. results.Object]; } return _lazyDependencySets; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs index 56a51c3f5750a..d7e48d3bf3dd3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs @@ -66,7 +66,7 @@ private static ImmutableDictionary> Compu } else { - return existingReferencesMap.SetItem(projectId, referencedProjectIds.ToImmutableHashSet()); + return existingReferencesMap.SetItem(projectId, [.. referencedProjectIds]); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 899adeef5bfe6..960c7f165945d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -1176,7 +1176,7 @@ public SolutionCompilationState WithFrozenSourceGeneratedDocuments( var documentStatesByProjectId = documentStates.ToDictionary(static state => state.Id.ProjectId); var newTrackerMap = CreateCompilationTrackerMap( - documentStatesByProjectId.Keys.ToImmutableArray(), + [.. documentStatesByProjectId.Keys], this.SolutionState.GetProjectDependencyGraph(), static (trackerMap, arg) => { @@ -1570,7 +1570,7 @@ private SolutionCompilationState RemoveDocumentsFromMultipleProjects( var removedDocumentStatesForProject = removedDocumentStates.ToImmutable(); - var compilationTranslationAction = removeDocumentsFromProjectState(oldProjectState, documentIdsInProject.ToImmutableArray(), removedDocumentStatesForProject); + var compilationTranslationAction = removeDocumentsFromProjectState(oldProjectState, [.. documentIdsInProject], removedDocumentStatesForProject); var newProjectState = compilationTranslationAction.NewProjectState; var stateChange = newCompilationState.SolutionState.ForkProject( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 4d3f278d7554a..6bf1728591683 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -1178,7 +1178,7 @@ public static ProjectDependencyGraph CreateDependencyGraph( state.ProjectReferences.Where(pr => projectStates.ContainsKey(pr.ProjectId)).Select(pr => pr.ProjectId).ToImmutableHashSet())) .ToImmutableDictionary(); - return new ProjectDependencyGraph(projectIds.ToImmutableHashSet(), map); + return new ProjectDependencyGraph([.. projectIds], map); } public SolutionState WithOptions(SolutionOptionSet options) diff --git a/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs b/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs index 4c40c9a8b6565..f948925ecc2f0 100644 --- a/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs +++ b/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs @@ -107,7 +107,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) if (_nested) { - fixes = [CodeAction.Create("Container", fixes.ToImmutableArray(), isInlinable: false)]; + fixes = [CodeAction.Create("Container", [.. fixes], isInlinable: false)]; } foreach (var fix in fixes) diff --git a/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs b/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs index c1a68021dae2b..4c80e4cd799c2 100644 --- a/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs +++ b/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs @@ -23,7 +23,7 @@ internal static EditorConfigFile CreateParseResults(string e list.Add(parseResult); } - return new EditorConfigFile(editorconfigFilePath, list.ToImmutableArray()); + return new EditorConfigFile(editorconfigFilePath, [.. list]); } [Fact] diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs index 8f28f33ba1510..b5d5d44d9e1dd 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs @@ -62,7 +62,7 @@ private static void Verify(SolutionKind workspaceKind, IEnumerable decl private static void VerifyResults(IEnumerable declarations, string[] expectedResults) { declarations = declarations.OrderBy(d => d.ToString()); - expectedResults = expectedResults.OrderBy(r => r).ToArray(); + expectedResults = [.. expectedResults.OrderBy(r => r)]; for (var i = 0; i < expectedResults.Length; i++) { diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index db2774e7943e2..6f023828ec35f 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -3935,7 +3935,7 @@ public void TestUpdateDocumentsOrder() var pid = ProjectId.CreateNewId(); VersionStamp GetVersion() => solution.GetProject(pid).Version; - ImmutableArray GetDocumentIds() => solution.GetProject(pid).DocumentIds.ToImmutableArray(); + ImmutableArray GetDocumentIds() => [.. solution.GetProject(pid).DocumentIds]; ImmutableArray GetSyntaxTrees() { return solution.GetProject(pid).GetCompilationAsync().Result.SyntaxTrees.ToImmutableArray(); diff --git a/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs b/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs index 63807101f0a66..b9e1680f9a148 100644 --- a/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs @@ -31,7 +31,7 @@ public CustomizedCanApplyWorkspace(ApplyChangesKind[] allowedKinds, Func? canApplyCompilationOptions = null) : base(Host.Mef.MefHostServices.DefaultHost, workspaceKind: nameof(CustomizedCanApplyWorkspace)) { - _allowedKinds = allowedKinds.ToImmutableArray(); + _allowedKinds = [.. allowedKinds]; _canApplyParseOptions = canApplyParseOptions; _canApplyCompilationOptions = canApplyCompilationOptions; diff --git a/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs b/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs index da2266620118d..95f12c9ac6ba3 100644 --- a/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs +++ b/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs @@ -33,9 +33,9 @@ public sealed class TestComposition public CacheKey(ImmutableHashSet assemblies, ImmutableHashSet parts, ImmutableHashSet excludedPartTypes) { - _assemblies = assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); - _parts = parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); - _excludedPartTypes = excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); + _assemblies = [.. assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; + _parts = [.. parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; + _excludedPartTypes = [.. excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; } public override bool Equals(object? obj) diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index ef18057196fcd..565746f8dc581 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -902,7 +902,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)netcore30).HasValue && ((bool?)netcore30).Value) { - references = NetCoreApp.References.ToList(); + references = [.. NetCoreApp.References]; } var netstandard20 = element.Attribute(CommonReferencesNetStandard20Name); @@ -910,7 +910,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)netstandard20).HasValue && ((bool?)netstandard20).Value) { - references = TargetFrameworkUtil.NetStandard20References.ToList(); + references = [.. TargetFrameworkUtil.NetStandard20References]; } var net6 = element.Attribute(CommonReferencesNet6Name); @@ -918,7 +918,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net6).HasValue && ((bool?)net6).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net60).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net60)]; } var net7 = element.Attribute(CommonReferencesNet7Name); @@ -926,7 +926,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net7).HasValue && ((bool?)net7).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net70).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net70)]; } var net8 = element.Attribute(CommonReferencesNet8Name); @@ -934,7 +934,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net8).HasValue && ((bool?)net8).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net80).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net80)]; } var mincorlib = element.Attribute(CommonReferencesMinCorlibName); diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs index 309159decf2be..af62f5337cb67 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs @@ -638,7 +638,7 @@ internal void InitializeDocuments( Documents.Add(submission.Documents.Single()); } - var solution = CreateSolution(projectNameToTestHostProject.Values.ToArray()); + var solution = CreateSolution([.. projectNameToTestHostProject.Values]); AddTestSolution(solution); foreach (var projectElement in workspaceElement.Elements(ProjectElementName)) diff --git a/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs b/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs index 0b331f550e052..1b405b7c66a44 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs @@ -41,7 +41,7 @@ public ValueTask IsCompletedAsync(ImmutableArray featureNames, Can var exportProvider = workspace.Services.SolutionServices.ExportProvider; var listenerProvider = exportProvider.GetExports().Single().Value; - return new ValueTask(!listenerProvider.HasPendingWaiter(featureNames.ToArray())); + return new ValueTask(!listenerProvider.HasPendingWaiter([.. featureNames])); }, cancellationToken); } @@ -53,7 +53,7 @@ public ValueTask ExpeditedWaitAsync(ImmutableArray featureNames, Cancell var exportProvider = workspace.Services.SolutionServices.ExportProvider; var listenerProvider = exportProvider.GetExports().Single().Value; - await listenerProvider.WaitAllAsync(workspace, featureNames.ToArray()).ConfigureAwait(false); + await listenerProvider.WaitAllAsync(workspace, [.. featureNames]).ConfigureAwait(false); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index 2db59acf92969..e593b1d8928de 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -52,7 +52,7 @@ await AbstractClassificationService.AddClassificationsInCurrentProcessAsync( _workQueue.AddWork((document, type, options)); } - return SerializableClassifiedSpans.Dehydrate(temp.ToImmutableArray()); + return SerializableClassifiedSpans.Dehydrate([.. temp]); }, cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs index e18cb1274e9d3..2e538e86dd901 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs @@ -58,8 +58,7 @@ public IList FormatRange( // Exception 2: Similar behavior for do-while if (common.ContainsDiagnostics && !CloseBraceOfTryOrDoBlock(endToken)) { - smartTokenformattingRules = ImmutableArray.Empty.Add( - new NoLineChangeFormattingRule()).AddRange(_formattingRules); + smartTokenformattingRules = [new NoLineChangeFormattingRule(), .. _formattingRules]; } var formatter = CSharpSyntaxFormatting.Instance; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs index e1509de06a403..da3c86ea8f721 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs @@ -393,7 +393,7 @@ private static ImmutableArray GetInterfacesToImplement( cancellationToken.ThrowIfCancellationRequested(); interfacesToImplement.RemoveRange(alreadyImplementedInterfaces); - return interfacesToImplement.ToImmutableArray(); + return [.. interfacesToImplement]; } private static ImmutableArray GetUnimplementedMembers( @@ -560,7 +560,7 @@ public static ImmutableArray GetOverridableMembers( RemoveNonOverriddableMembers(result, containingType, cancellationToken); } - return result.Keys.OrderBy(s => result[s]).ToImmutableArray(); + return [.. result.Keys.OrderBy(s => result[s])]; static void RemoveOverriddenMembers( Dictionary result, INamedTypeSymbol containingType, CancellationToken cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs index c5cf608238719..fa6a76cf05a04 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs @@ -86,7 +86,7 @@ protected AnalysisData() /// public SymbolUsageResult ToResult() => new(SymbolsWriteBuilder.ToImmutableDictionary(), - SymbolsReadBuilder.ToImmutableHashSet()); + [.. SymbolsReadBuilder]); public BasicBlockAnalysisData AnalyzeLocalFunctionInvocation(IMethodSymbol localFunction, CancellationToken cancellationToken) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs index 1a25bc7db5577..8b0b6e1b3b97a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs @@ -196,7 +196,7 @@ private static ImmutableHashSet GetCapturedLocals(ControlFlowGraph } } - return builder.ToImmutableHashSet(); + return [.. builder]; } public BasicBlockAnalysisData GetBlockAnalysisData(BasicBlock basicBlock) @@ -600,7 +600,7 @@ public override bool TryGetDelegateInvocationTargets(IOperation write, out Immut // Attempts to return potential lamba/local function delegate invocation targets for the given write. if (_reachingDelegateCreationTargets.TryGetValue(write, out var targetsBuilder)) { - targets = targetsBuilder.ToImmutableHashSet(); + targets = [.. targetsBuilder]; return true; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs index 99aeca8efb44c..4bf5a87ee261d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs @@ -157,7 +157,7 @@ public TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives BuildFoldersFromNamespace(string? @namespac } var parts = @namespace.Split(NamespaceSeparatorArray, options: StringSplitOptions.RemoveEmptyEntries); - return parts.ToImmutableArray(); + return [.. parts]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs index 9886c1975190c..83d4ad3474067 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs @@ -10,5 +10,5 @@ namespace System.Runtime.CompilerServices; internal sealed class RestrictedInternalsVisibleToAttribute(string assemblyName, params string[] allowedNamespaces) : Attribute { public string AssemblyName { get; } = assemblyName; - public ImmutableArray AllowedNamespaces { get; } = allowedNamespaces.ToImmutableArray(); + public ImmutableArray AllowedNamespaces { get; } = [.. allowedNamespaces]; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs index aaeae082f5d20..884ebc4e3f51d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs @@ -501,7 +501,7 @@ public override TDeclarationNode AddStatements( } else if (destinationMember is AccessorDeclarationSyntax accessorDeclaration) { - return (accessorDeclaration.Body == null) ? destinationMember : Cast(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray())); + return (accessorDeclaration.Body == null) ? destinationMember : Cast(accessorDeclaration.AddBodyStatements([.. StatementGenerator.GenerateStatements(statements)])); } else if (destinationMember is CompilationUnitSyntax compilationUnit && info.Context.BestLocation is null) { @@ -520,7 +520,7 @@ public override TDeclarationNode AddStatements( // statement container. If the global statement is not already a block, create a block which can hold // both the original statement and any new statements we are adding to it. var block = statement as BlockSyntax ?? Block(statement); - return Cast(block.AddStatements(StatementGenerator.GenerateStatements(statements).ToArray())); + return Cast(block.AddStatements([.. StatementGenerator.GenerateStatements(statements)])); } else { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs index 09b58b5e6a7fe..3839eb370ebbc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs @@ -71,7 +71,7 @@ public static void GetNameAndInnermostNamespace( break; } - name = string.Join(".", names.ToArray()); + name = string.Join(".", [.. names]); } else { @@ -336,7 +336,7 @@ public static int GetInsertionIndex( // The list was grouped (by type, staticness, accessibility). Try to find a location // to put the new declaration into. - var result = Array.BinarySearch(declarationList.ToArray(), declaration, comparerWithoutNameCheck); + var result = Array.BinarySearch([.. declarationList], declaration, comparerWithoutNameCheck); var desiredGroupIndex = result < 0 ? ~result : result; Debug.Assert(desiredGroupIndex >= 0); Debug.Assert(desiredGroupIndex <= declarationList.Count); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 5d525a7b31c10..45c058621bda5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -109,7 +109,7 @@ public virtual ImmutableArray ReturnTypeCustomModifiers public ImmutableArray UnmanagedCallingConventionTypes => []; public IMethodSymbol Construct(params ITypeSymbol[] typeArguments) - => new CodeGenerationConstructedMethodSymbol(this, typeArguments.ToImmutableArray()); + => new CodeGenerationConstructedMethodSymbol(this, [.. typeArguments]); public IMethodSymbol Construct(ImmutableArray typeArguments, ImmutableArray typeArgumentNullableAnnotations) => new CodeGenerationConstructedMethodSymbol(this, typeArguments); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs index 5b06942521e2d..445328ff50423 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs @@ -63,7 +63,7 @@ public INamedTypeSymbol Construct(params ITypeSymbol[] typeArguments) } return new CodeGenerationConstructedNamedTypeSymbol( - ConstructedFrom, typeArguments.ToImmutableArray(), this.TypeMembers); + ConstructedFrom, [.. typeArguments], this.TypeMembers); } public INamedTypeSymbol Construct(ImmutableArray typeArguments, ImmutableArray typeArgumentNullableAnnotations) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs index fcf86c82b58ca..bc4197cc204c1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs @@ -27,7 +27,7 @@ public static ImmutableArray LoadNearbyAssemblies(IEnumerable } } - return assemblies.ToImmutableArray(); + return [.. assemblies]; } private static Assembly TryLoadNearbyAssembly(string assemblySimpleName) From 25dd646672af5657657a5a28fe1980655981b173 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:34:36 -0700 Subject: [PATCH 0292/1047] revert --- .../CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs | 8 ++++---- .../Core/Source/ResultProvider/Helpers/ArrayBuilder.cs | 2 +- .../Core/Test/FunctionResolver/Process.cs | 2 +- .../Core/Test/FunctionResolver/Resolver.cs | 2 +- .../Core/Test/ResultProvider/ReflectionUtilities.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs index 1c629b457c386..27a7d762f5b1c 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs @@ -382,9 +382,9 @@ public void BadPdb_ForwardChain() { switch (token) { - case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes]; - case methodToken2: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes]; - case methodToken3: return [.. new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes]; + case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes.ToArray(); + case methodToken2: return new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes.ToArray(); + case methodToken3: return new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes.ToArray(); default: throw null; } }); @@ -421,7 +421,7 @@ public void BadPdb_Cycle() { switch (token) { - case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes]; + case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes.ToArray(); default: throw null; } }); diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs index b87194ad1065d..89e03e291c67e 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs @@ -91,7 +91,7 @@ public void Free() public T[] ToArray() { - return [.. _items]; + return _items.ToArray(); } public T[] ToArrayAndFree() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs index 1da9a346b6325..adbf1368e2732 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs @@ -40,7 +40,7 @@ internal void AddModule(Module module) internal Module[] GetModules() { - return [.. _modules]; + return _modules.ToArray(); } void IDisposable.Dispose() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs index 04651d7b9dc9a..6f9f3d7ad366f 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs @@ -73,7 +73,7 @@ internal override Request[] GetRequests(Process process) { return new Request[0]; } - return [.. requests]; + return requests.ToArray(); } internal override string GetRequestModuleName(Request request) diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs index 4f43d28015f4f..c46088fa8a4c1 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs @@ -16,7 +16,7 @@ internal static class ReflectionUtilities { internal static Assembly Load(ImmutableArray assembly) { - return Assembly.Load([.. assembly]); + return Assembly.Load(assembly.ToArray()); } internal static object Instantiate(this Type type, params object[] args) From 0296c7c34a717f91e1652c603384adb5b90b4f17 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:37:11 -0700 Subject: [PATCH 0293/1047] revert --- .../Protocol/Features/DecompiledSource/AssemblyResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs index e68ef976c26f6..00d5807883c73 100644 --- a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs +++ b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs @@ -64,7 +64,7 @@ public PEFile TryResolve(MetadataReference metadataReference, PEStreamOptions st { if (_inMemoryImagesForTesting.TryGetValue(metadataReference, out var pair)) { - return new PEFile(pair.fileName, new MemoryStream([.. pair.image]), streamOptions); + return new PEFile(pair.fileName, new MemoryStream(pair.image.ToArray()), streamOptions); } return null; From 727b0da6676bb7849d3ece5879deab7e4c0d78c9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:39:52 -0700 Subject: [PATCH 0294/1047] revert --- .../Host/Interactive/Core/InteractiveHostPlatformInfo.cs | 2 +- .../Host/Interactive/Core/RemoteExecutionResult.cs | 4 ++-- .../Host/Interactive/Core/RemoteInitializationResult.cs | 4 ++-- .../InteractiveSessionReferencesTests.cs | 4 ++-- src/Scripting/Core/Hosting/SynchronizedList.cs | 2 +- .../PdbUtilities/EditAndContinue/EditAndContinueTest.cs | 4 ++-- src/Test/PdbUtilities/Reader/SymReaderFactory.cs | 2 +- src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs | 2 +- src/Tools/BuildActionTelemetryTable/Program.cs | 5 +++-- src/Tools/BuildValidator/CompilationDiff.cs | 8 ++++---- src/Tools/BuildValidator/Program.cs | 4 ++-- .../Razor/RazorSpanMappingServiceWrapper.cs | 2 +- src/Tools/Replay/Replay.cs | 2 +- .../BuildTask/GenerateFilteredReferenceAssembliesTask.cs | 8 ++++---- src/Tools/Source/RunTests/AssemblyScheduler.cs | 4 ++-- src/Tools/Source/RunTests/Program.cs | 2 +- src/Tools/Source/RunTests/TestRunner.cs | 2 +- 17 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs index 0f901c2bb2478..8066dae54677e 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs @@ -44,7 +44,7 @@ public Data Serialize() => new Data() { HasGlobalAssemblyCache = HasGlobalAssemblyCache, - PlatformAssemblyPaths = [.. PlatformAssemblyPaths], + PlatformAssemblyPaths = PlatformAssemblyPaths.ToArray(), }; public static InteractiveHostPlatformInfo GetCurrentPlatformInfo() diff --git a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs index 7b54f0437ba74..1fd4f2c9b7e87 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs @@ -64,8 +64,8 @@ public Data Serialize() => new Data() { Success = Success, - SourcePaths = [.. SourcePaths], - ReferencePaths = [.. ReferencePaths], + SourcePaths = SourcePaths.ToArray(), + ReferencePaths = ReferencePaths.ToArray(), WorkingDirectory = WorkingDirectory, InitializationResult = InitializationResult?.Serialize(), }; diff --git a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs index e1cac0b878d68..7dc06aaabe5b2 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs @@ -43,8 +43,8 @@ public Data Serialize() => new Data() { ScriptPath = ScriptPath, - MetadataReferencePaths = [.. MetadataReferencePaths], - Imports = [.. Imports], + MetadataReferencePaths = MetadataReferencePaths.ToArray(), + Imports = Imports.ToArray(), }; } } diff --git a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs index ceaeb09bbb656..c236c73222d8f 100644 --- a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs +++ b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs @@ -532,7 +532,7 @@ public async Task MissingRefrencesAutoResolution() var portableLibRef = portableLib.ToMetadataReference(); var loader = new InteractiveAssemblyLoader(); - loader.RegisterDependency(Assembly.Load([.. portableLib.EmitToArray()])); + loader.RegisterDependency(Assembly.Load(portableLib.EmitToArray().ToArray())); var s0 = await CSharpScript.Create("new C()", options: ScriptOptions.Default.AddReferences(portableLibRef), assemblyLoader: loader).RunAsync(); var c0 = s0.Script.GetCompilation(); @@ -569,7 +569,7 @@ public void HostObjectInInMemoryAssembly() var libImage = lib.EmitToArray(); var libRef = MetadataImageReference.CreateFromImage(libImage); - var libAssembly = Assembly.Load([.. libImage]); + var libAssembly = Assembly.Load(libImage.ToArray()); var globalsType = libAssembly.GetType("C"); var globals = Activator.CreateInstance(globalsType); diff --git a/src/Scripting/Core/Hosting/SynchronizedList.cs b/src/Scripting/Core/Hosting/SynchronizedList.cs index a6e399afb7a69..1b99490d96b50 100644 --- a/src/Scripting/Core/Hosting/SynchronizedList.cs +++ b/src/Scripting/Core/Hosting/SynchronizedList.cs @@ -83,7 +83,7 @@ public IEnumerator GetEnumerator() lock (_guard) { // make a copy to ensure thread-safe enumeration - return ((IEnumerable)[.. _list]).GetEnumerator(); + return ((IEnumerable)_list.ToArray()).GetEnumerator(); } } diff --git a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs index 3cdb0a2d003c0..f20f773ac3597 100644 --- a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs +++ b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs @@ -117,10 +117,10 @@ internal TSelf Verify() } readers.Add(generation.MetadataReader); - var verifier = new GenerationVerifier(index, generation, [.. readers]); + var verifier = new GenerationVerifier(index, generation, readers.ToImmutableArray()); generation.Verifier(verifier); - exceptions.Add([.. verifier.Exceptions]); + exceptions.Add(verifier.Exceptions.ToImmutableArray()); index++; } diff --git a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs index d15171d69a9cd..8968f175f0b5a 100644 --- a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs +++ b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs @@ -72,7 +72,7 @@ public static ISymUnmanagedReader5 CreateReader(byte[] pdbImage, byte[] peImageO public static ISymUnmanagedReader5 CreateReader(ImmutableArray pdbImage, ImmutableArray peImageOpt = default(ImmutableArray)) { - return CreateReader(new MemoryStream([.. pdbImage]), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); + return CreateReader(new MemoryStream(pdbImage.ToArray()), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); } public static ISymUnmanagedReader5 CreateReader(Stream pdbStream, Stream peStreamOpt = null) diff --git a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs index f5dffe99106d3..61de9bce50d17 100644 --- a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs +++ b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs @@ -1467,7 +1467,7 @@ private static void LoadDbiStream(BitAccess bits, out DbiModuleInfo[] modules, o if (modList.Count > 0) { - modules = [.. modList]; + modules = modList.ToArray(); } else { diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index 0adb386609d4f..d03b7331e2d4e 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -493,10 +493,11 @@ static bool isCodeActionProviderType(Type t) => typeof(CodeFixProvider).IsAssign internal static ImmutableArray<(string TypeName, string Hash)> GetTelemetryInfos(ImmutableArray codeActionAndProviderTypes) { - return [.. codeActionAndProviderTypes + return codeActionAndProviderTypes .Distinct(FullNameTypeComparer.Instance) .Select(GetTelemetryInfo) - .OrderBy(info => info.TypeName)]; + .OrderBy(info => info.TypeName) + .ToImmutableArray(); static (string TypeName, string Hash) GetTelemetryInfo(Type type) { diff --git a/src/Tools/BuildValidator/CompilationDiff.cs b/src/Tools/BuildValidator/CompilationDiff.cs index ed741dbe60ccb..a8d24aca794c2 100644 --- a/src/Tools/BuildValidator/CompilationDiff.cs +++ b/src/Tools/BuildValidator/CompilationDiff.cs @@ -176,7 +176,7 @@ MetadataReader getRebuildPdbReader() { if (hasEmbeddedPdb) { - var peReader = new PEReader([.. rebuildBytes]); + var peReader = new PEReader(rebuildBytes.ToImmutableArray()); return peReader.GetEmbeddedPdbMetadataReader() ?? throw ExceptionUtilities.Unreachable(); } else @@ -261,8 +261,8 @@ void writeBinaryDiffArtifacts() Debug.Assert(_rebuildPdbReader is object); Debug.Assert(_rebuildCompilation is object); - var originalPeReader = new PEReader([.. _originalPortableExecutableBytes]); - var rebuildPeReader = new PEReader([.. _rebuildPortableExecutableBytes]); + var originalPeReader = new PEReader(_originalPortableExecutableBytes.ToImmutableArray()); + var rebuildPeReader = new PEReader(_rebuildPortableExecutableBytes.ToImmutableArray()); var originalInfo = new BuildInfo( AssemblyBytes: _originalPortableExecutableBytes, AssemblyReader: originalPeReader, @@ -401,7 +401,7 @@ void writeEmbeddedFileInfo() { if (!info.CompressedHash.IsDefaultOrEmpty) { - var hashString = BitConverter.ToString([.. info.CompressedHash]).Replace("-", ""); + var hashString = BitConverter.ToString(info.CompressedHash.ToArray()).Replace("-", ""); writer.WriteLine($@"\t""{Path.GetFileName(info.SourceTextInfo.OriginalSourceFilePath)}"" - {hashString}"); } } diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 7b74aa2bb1ada..7f19bb818826a 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -108,7 +108,7 @@ static int HandleCommand(string[] assembliesPath, string[]? exclude, string sour excludes.Add(Path.DirectorySeparatorChar + "runtimes" + Path.DirectorySeparatorChar); excludes.Add(@".resources.dll"); - var options = new Options(assembliesPath, referencesPath, [.. excludes], sourcePath, verbose, quiet, debug, debugPath); + var options = new Options(assembliesPath, referencesPath, excludes.ToArray(), sourcePath, verbose, quiet, debug, debugPath); // TODO: remove the DemoLoggerProvider or convert it to something more permanent var loggerFactory = LoggerFactory.Create(builder => @@ -221,7 +221,7 @@ private static AssemblyInfo[] GetAssemblyInfos( } } - return [.. map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer)]; + return map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer).ToArray(); static IEnumerable getAssemblyPaths(string directory) { diff --git a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs index 64b1a31fdfa0d..d186da23ac528 100644 --- a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs @@ -65,7 +65,7 @@ public override async Task> MapSpansAsync( } } - return [.. roslynSpans]; + return roslynSpans.ToImmutableArray(); } } } diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs index 2467e612b5105..444b79364ae03 100644 --- a/src/Tools/Replay/Replay.cs +++ b/src/Tools/Replay/Replay.cs @@ -196,7 +196,7 @@ static async Task BuildAsync( var request = BuildServerConnection.CreateBuildRequest( outputName, compilerCall.IsCSharp ? RequestLanguage.CSharpCompile : RequestLanguage.VisualBasicCompile, - [.. args], + args.ToList(), workingDirectory: compilerCall.ProjectDirectory, tempDirectory: options.TempDirectory, keepAlive: null, diff --git a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs index 8c18e4f662dc1..1bfd842325f35 100644 --- a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs +++ b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs @@ -106,7 +106,7 @@ internal void ExecuteImpl(IEnumerable<(string apiSpecPath, IReadOnlyList } var peImageBuffer = File.ReadAllBytes(originalReferencePath); - Rewrite(peImageBuffer, [.. patterns]); + Rewrite(peImageBuffer, patterns.ToImmutableArray()); try { @@ -302,21 +302,21 @@ internal static unsafe void Rewrite(byte[] peImage, ImmutableArray p writer, metadataReader, patterns, - [.. types.OrderBy(t => t.MetadataToken)], + types.OrderBy(t => t.MetadataToken).ToImmutableArray(), metadataOffset); UpdateMethodDefinitions( writer, metadataReader, patterns, - [.. methods.OrderBy(t => t.MetadataToken)], + methods.OrderBy(t => t.MetadataToken).ToImmutableArray(), metadataOffset); UpdateFieldDefinitions( writer, metadataReader, patterns, - [.. fields.OrderBy(t => t.MetadataToken)], + fields.OrderBy(t => t.MetadataToken).ToImmutableArray(), metadataOffset); // unsign: diff --git a/src/Tools/Source/RunTests/AssemblyScheduler.cs b/src/Tools/Source/RunTests/AssemblyScheduler.cs index 56eb6bb308f1f..4f54d609f98a8 100644 --- a/src/Tools/Source/RunTests/AssemblyScheduler.cs +++ b/src/Tools/Source/RunTests/AssemblyScheduler.cs @@ -112,7 +112,7 @@ static ImmutableArray CreateWorkItemsForFullAssemblies(ImmutableAr workItems.Add(new WorkItemInfo(currentWorkItem, partitionIndex++)); } - return [.. workItems]; + return workItems.ToImmutableArray(); } } @@ -235,7 +235,7 @@ private ImmutableArray BuildWorkItems( // Add any remaining tests to the work item. AddCurrentWorkItem(); - return [.. workItems]; + return workItems.ToImmutableArray(); void AddCurrentWorkItem() { diff --git a/src/Tools/Source/RunTests/Program.cs b/src/Tools/Source/RunTests/Program.cs index 121cd0dc85dc9..104bdcad356bb 100644 --- a/src/Tools/Source/RunTests/Program.cs +++ b/src/Tools/Source/RunTests/Program.cs @@ -351,7 +351,7 @@ private static ImmutableArray GetAssemblyFilePaths(Options options } list.Sort(); - return [.. list]; + return list.ToImmutableArray(); static bool shouldInclude(string name, Options options) { diff --git a/src/Tools/Source/RunTests/TestRunner.cs b/src/Tools/Source/RunTests/TestRunner.cs index 2d21960723ac5..f837f34a2e8d9 100644 --- a/src/Tools/Source/RunTests/TestRunner.cs +++ b/src/Tools/Source/RunTests/TestRunner.cs @@ -406,7 +406,7 @@ internal async Task RunAllAsync(ImmutableArray workI processResults.AddRange(c.ProcessResults); } - return new RunAllResult((failures == 0), [.. completed], processResults.ToImmutable()); + return new RunAllResult((failures == 0), completed.ToImmutableArray(), processResults.ToImmutable()); } private void Print(List testResults) From 72d701e06271669a7eace4c70fe4561002d0080d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 19:09:05 -0700 Subject: [PATCH 0295/1047] Switch to ToImmutableAndClear --- .../UseCollectionExpressionHelpers.cs | 2 +- .../ConvertToRecord/ConvertToRecordHelpers.cs | 4 ++-- .../PositionalParameterInfo.cs | 2 +- ...MisplacedUsingDirectivesCodeFixProvider.cs | 2 +- .../UseSimpleUsingStatementCodeFixProvider.cs | 2 +- .../UseCollectionExpressionForBuilderTests.cs | 18 ++++++++--------- ...bstractObjectCreationExpressionAnalyzer.cs | 2 +- .../Core/CodeFixes/AddExplicitCast/Fixer.cs | 2 +- .../AbstractAddParameterCodeFixProvider.cs | 4 ++-- ...derCodeFixProvider.CustomFixAllProvider.cs | 2 +- .../KnownSourcePasteProcessor.cs | 2 +- .../UnknownSourcePasteProcessor.cs | 2 +- .../Core.Wpf/Peek/PeekableItemFactory.cs | 2 +- ...ndentationAdornmentManager.VisibleBlock.cs | 2 +- .../DefinitionContextTracker.cs | 2 +- .../NavigationBar/WrappedNavigationBarItem.cs | 2 +- .../IntelliCode/IntentProcessor.cs | 2 +- .../IntelliSense/AsyncCompletion/FilterSet.cs | 4 ++-- ...igateToHighlightReferenceCommandHandler.cs | 2 +- .../CodeGeneration/CodeGenerationTests.cs | 2 +- .../Snippets/RoslynLSPSnippetConvertTests.cs | 2 +- ...actCurlyBraceOrBracketCompletionService.cs | 2 +- .../CSharpChangeSignatureService.cs | 2 +- .../CodeLens/CSharpCodeLensMemberFinder.cs | 2 +- .../DeclarationNameRecommender.cs | 2 +- .../ConvertProgramTransform_ProgramMain.cs | 2 +- ...vertProgramTransform_TopLevelStatements.cs | 2 +- .../CSharpDocumentHighlightsService.cs | 2 +- ...nerator.MultipleStatementsCodeGenerator.cs | 2 +- ...harpMethodExtractor.CSharpCodeGenerator.cs | 2 +- .../CSharpMethodExtractor.PostProcessor.cs | 2 +- ...SharpGenerateParameterizedMemberService.cs | 4 ++-- .../CSharpStructuralTypeDisplayService.cs | 2 +- .../CSharpLineSeparatorService.cs | 2 +- .../CSharpNavigationBarItemService.cs | 2 +- ...CSharpReplacePropertyWithMethodsService.cs | 2 +- .../CSharpForEachLoopSnippetProvider.cs | 2 +- .../CSharpStringIndentationService.cs | 2 +- ...ionBodyForLambdaCodeRefactoringProvider.cs | 2 +- ...emoveUnnecessaryPragmaSuppressionsTests.cs | 2 +- ...romMembersCodeRefactoringProvider.State.cs | 2 +- ...etersFromMembersCodeRefactoringProvider.cs | 4 ++-- ...actAddFileBannerCodeRefactoringProvider.cs | 2 +- .../AbstractAddImportFeatureService.cs | 8 ++++---- .../InstallPackageAndAddImportCodeAction.cs | 2 +- .../AddImport/SymbolReferenceFinder.cs | 2 +- .../ParentInstallPackageCodeAction.cs | 2 +- .../AbstractChangeSignatureService.cs | 4 ++-- .../DelegateInvokeMethodReferenceFinder.cs | 2 +- .../Configuration/ConfigurationUpdater.cs | 2 +- .../AbstractRefactoringHelpersService.cs | 2 +- ...actExtractMethodCodeRefactoringProvider.cs | 2 +- .../MoveType/AbstractMoveTypeService.cs | 2 +- .../AbstractChangeNamespaceService.cs | 2 +- .../AbstractProjectExtensionProvider.cs | 4 ++-- .../AbstractDocCommentCompletionProvider.cs | 2 +- .../AbstractKeywordCompletionProvider.cs | 2 +- .../AbstractSymbolCompletionProvider.cs | 4 ++-- .../AbstractImportCompletionProvider.cs | 2 +- ...odImportCompletionHelper.SymbolComputer.cs | 4 ++-- .../ExtensionMethodImportCompletionHelper.cs | 2 +- .../TypeImportCompletionCacheEntry.cs | 2 +- ...AbstractLoadDirectiveCompletionProvider.cs | 2 +- ...actReferenceDirectiveCompletionProvider.cs | 2 +- .../GlobalAssemblyCacheCompletionHelper.cs | 2 +- .../Providers/SymbolCompletionItem.cs | 2 +- ...ertTupleToStructCodeRefactoringProvider.cs | 6 +++--- .../DesignerAttributeDiscoveryService.cs | 2 +- .../AbstractEditAndContinueAnalyzer.cs | 2 +- .../EditAndContinue/ActiveStatementsMap.cs | 2 +- .../EditAndContinue/DebuggingSession.cs | 4 ++-- .../EditAndContinueCapabilities.cs | 2 +- .../EditAndContinueDocumentAnalysesCache.cs | 2 +- .../Portable/EditAndContinue/EditSession.cs | 2 +- .../EmitSolutionUpdateResults.cs | 4 ++-- .../EditAndContinue/ProjectDiagnostics.cs | 2 +- .../RemoteEditAndContinueServiceProxy.cs | 2 +- .../EmbeddedLanguages/Json/JsonLexer.cs | 2 +- .../EmbeddedLanguages/Json/JsonParser.cs | 2 +- .../RegularExpressions/RegexLexer.cs | 2 +- .../StackFrame/StackFrameLexer.cs | 2 +- .../AbstractEncapsulateFieldService.cs | 2 +- .../API/UnitTestingSearchHelpers.cs | 4 ++-- .../AbstractExtractInterfaceService.cs | 2 +- .../ExtractMethod/MethodExtractor.Analyzer.cs | 2 +- .../MethodExtractor.CodeGenerator.cs | 2 +- ...bstractFindUsagesService_FindReferences.cs | 2 +- ...parisonOperatorsCodeRefactoringProvider.cs | 2 +- ...uctorFromMembersCodeRefactoringProvider.cs | 4 ++-- ...tractGenerateDefaultConstructorsService.cs | 2 +- ...erateFromMembersCodeRefactoringProvider.cs | 2 +- .../AbstractGenerateConstructorService.cs | 2 +- .../AbstractGenerateMethodService.State.cs | 2 +- ...arameterizedMemberService.SignatureInfo.cs | 2 +- ...tractGenerateParameterizedMemberService.cs | 2 +- .../AbstractGenerateVariableService.cs | 2 +- ...ctGenerateTypeService.GenerateNamedType.cs | 2 +- .../AbstractGenerateTypeService.cs | 4 ++-- .../GoToDefinitionFeatureHelpers.cs | 2 +- .../ImplementAbstractClassData.cs | 2 +- ...actImplementInterfaceService.CodeAction.cs | 2 +- .../ImplementInterface/ImplementHelpers.cs | 2 +- ...bstractInheritanceMarginService_Helpers.cs | 8 ++++---- ...erCodeRefactoringProviderMemberCreation.cs | 4 ++-- .../InitializeParameterHelpersCore.cs | 2 +- ...AbstractInlineParameterNameHintsService.cs | 2 +- .../AbstractInlineTypeHintsService.cs | 2 +- ...efactoringProvider.MethodParametersInfo.cs | 2 +- ...AbstractInvertIfCodeRefactoringProvider.cs | 2 +- .../AbstractStructuralTypeDisplayService.cs | 4 ++-- ...dataAsSourceService.DocCommentFormatter.cs | 2 +- .../AbstractMoveToNamespaceCodeAction.cs | 2 +- ...stractNavigateToSearchService.InProcess.cs | 2 +- .../Portable/NavigateTo/NavigateToSearcher.cs | 2 +- .../DocumentDebugInfoReader.cs | 2 +- ...rceDocumentMetadataAsSourceFileProvider.cs | 2 +- ...thodWithPropertyCodeRefactoringProvider.cs | 2 +- ...pertyWithMethodsCodeRefactoringProvider.cs | 2 +- .../Shared/Utilities/ExtractTypeHelpers.cs | 4 ++-- .../AbstractSignatureHelpProvider.cs | 2 +- .../Snippets/AbstractSnippetService.cs | 2 +- .../AbstractSpellCheckSpanService.cs | 2 +- .../StackTraceExplorer/StackTraceAnalyzer.cs | 2 +- .../TaskList/AbstractTaskListService.cs | 2 +- .../UnusedReferencesRemover.cs | 6 +++--- .../Workspace/CompileTimeSolutionProvider.cs | 2 +- .../AbstractBinaryExpressionWrapper.cs | 2 +- .../BinaryExpressionCodeActionComputer.cs | 4 ++-- .../AbstractChainedExpressionWrapper.cs | 2 +- .../ChainedExpressionCodeActionComputer.cs | 4 ++-- .../SeparatedSyntaxListCodeActionComputer.cs | 6 +++--- .../Testing/TestDiscoverer.cs | 2 +- .../Features/CodeFixes/CodeFixService.cs | 4 ++-- ...ntalAnalyzer.StateManager.ProjectStates.cs | 2 +- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 2 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 2 +- .../UnifiedSuggestedActionsSource.cs | 2 +- .../Handler/CodeActions/CodeActionHelpers.cs | 4 ++-- ...dChangeConfigurationNotificationHandler.cs | 4 ++-- ...AbstractWorkspacePullDiagnosticsHandler.cs | 4 ++-- .../Handler/MapCode/MapCodeHandler.cs | 2 +- .../SpellCheck/WorkspaceSpellCheckHandler.cs | 2 +- .../Workspaces/LspWorkspaceManager.cs | 2 +- .../ActiveStatementsDescription.cs | 2 +- .../DocumentOutline/DocumentOutlineTests.cs | 2 +- .../RemoteCodeLensReferencesService.cs | 2 +- .../DocumentOutlineViewModel_Utilities.cs | 4 ++-- .../InheritanceMarginHelpers.cs | 2 +- .../ObjectBrowser/AbstractListItemFactory.cs | 20 +++++++++---------- .../PackageInstallerServiceFactory.cs | 2 +- .../Core/Def/Progression/GraphProvider.cs | 2 +- .../GraphQueries/CallsGraphQuery.cs | 2 +- .../Core/Def/Progression/SymbolContainment.cs | 2 +- .../Snippets/AbstractSnippetInfoService.cs | 2 +- ...ioDiagnosticListSuppressionStateService.cs | 2 +- .../VisualStudioSuppressionFixService.cs | 2 +- .../ExternalErrorDiagnosticUpdateSource.cs | 4 ++-- .../Services/ServiceHubServicesTests.cs | 2 +- .../CSharpRecommendationServiceRunner.cs | 2 +- .../CSharpRenameRewriterLanguageService.cs | 2 +- .../Core/MSBuild.BuildHost/BuildHost.cs | 2 +- .../MSBuild/ProjectFile/ProjectFile.cs | 2 +- .../MSBuild/MSBuildProjectLoader.Worker.cs | 4 ++-- ...dProjectLoader.Worker_ResolveReferences.cs | 4 ++-- .../FixAllOccurrences/BatchFixAllProvider.cs | 4 ++-- .../Diagnostics/DiagnosticAnalysisResult.cs | 2 +- .../Core/Portable/Diagnostics/Extensions.cs | 4 ++-- .../DeclarationFinder_AllDeclarations.cs | 2 +- .../DeclarationFinder_SourceDeclarations.cs | 4 ++-- .../DependentTypeFinder_Remote.cs | 2 +- .../FindReferences/FindReferenceCache.cs | 4 ++-- ...dOrPropertyOrEventSymbolReferenceFinder.cs | 2 +- .../Finders/AbstractReferenceFinder.cs | 10 +++++----- ...tractReferenceFinder_GlobalSuppressions.cs | 2 +- ...tractTypeParameterSymbolReferenceFinder.cs | 2 +- .../ConstructorSymbolReferenceFinder.cs | 4 ++-- ...ExplicitConversionSymbolReferenceFinder.cs | 2 +- .../Finders/NamedTypeSymbolReferenceFinder.cs | 4 ++-- .../Finders/NamespaceSymbolReferenceFinder.cs | 4 ++-- .../Finders/ParameterSymbolReferenceFinder.cs | 2 +- .../Finders/PropertySymbolReferenceFinder.cs | 2 +- .../FindSymbols/StreamingProgressCollector.cs | 2 +- ...SymbolFinder_Declarations_CustomQueries.cs | 2 +- .../AbstractReassignedVariableService.cs | 2 +- .../AbstractRecommendationServiceRunner.cs | 4 ++-- .../Core/Portable/Remote/RemoteUtilities.cs | 2 +- .../Rename/SymbolicRenameLocations.cs | 2 +- .../Extensions/IAsyncEnumerableExtensions.cs | 2 +- .../IFindReferencesResultExtensions.cs | 2 +- .../Extensions/ITypeSymbolExtensions.cs | 2 +- .../Extensions/SyntaxGeneratorExtensions.cs | 4 ++-- ...xGeneratorExtensions_CreateEqualsMethod.cs | 4 ++-- .../TaskList/TaskListItemDescriptor.cs | 2 +- .../AbstractSpanMappingService.cs | 2 +- .../DiagnosticAnalyzer/DiagnosticComputer.cs | 6 +++--- .../SymbolFinder/RemoteSymbolFinderService.cs | 2 +- .../SemanticFacts/CSharpSemanticFacts.cs | 2 +- .../Core/Extensions/ICollectionExtensions.cs | 2 +- .../TriviaEngine/AbstractTriviaFormatter.cs | 2 +- .../AbstractSelectedMembers.cs | 2 +- .../SymbolKey/SymbolKey.BodyLevelSymbolKey.cs | 2 +- .../SymbolKey/SymbolKey.ErrorTypeSymbolKey.cs | 2 +- .../CodeGeneration/ParameterGenerator.cs | 2 +- .../TypeDeclarationSyntaxExtensions.cs | 2 +- .../Core/Editing/AddParameterEditor.cs | 2 +- 205 files changed, 277 insertions(+), 277 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs index 30ce3438410cd..91530f6c07c7c 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs @@ -906,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/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/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/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs index 17199e80037c5..282bb5ea8acab 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseSimpleUsingStatement/UseSimpleUsingStatementCodeFixProvider.cs @@ -104,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) diff --git a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs index bd0fbe4312df9..40b463561fc63 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs @@ -77,7 +77,7 @@ ImmutableArray M() { {{pattern}} builder.Add(0); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -100,7 +100,7 @@ ImmutableArray M() { {{pattern}} builder.Add(0); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -123,7 +123,7 @@ ImmutableArray M() { {{pattern}} [|builder.Add(|]0); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -1256,7 +1256,7 @@ ImmutableArray M() // Leading [|builder.Add(|]0); // Trailing - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -1295,7 +1295,7 @@ ImmutableArray M() {{pattern}} [|builder.Add(|]1 + 2); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -1336,7 +1336,7 @@ ImmutableArray M() 2); [|builder.Add(|]3 + 4); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -1376,7 +1376,7 @@ ImmutableArray M() { using var _ = ArrayBuilder.GetInstance(10, 0, out var builder); builder.Add(0); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -1540,7 +1540,7 @@ IEnumerable M() { {{pattern}} [|builder.Add(|]0); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, @@ -1577,7 +1577,7 @@ IEnumerable M() { {{pattern}} builder.Add(0); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } """ + s_arrayBuilderApi, 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/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..eb9175b371549 100644 --- a/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/AddParameter/AbstractAddParameterCodeFixProvider.cs @@ -312,7 +312,7 @@ ImmutableArray NestByCascading() builder.Add(CodeAction.Create(nestedCascadingTitle, cascadingActions, isInlinable: false)); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } @@ -343,7 +343,7 @@ private ImmutableArray PrepareCreationOfCodeActions( builder.Add(codeFixData); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } 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/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/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/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/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/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/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/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/Test/CodeGeneration/CodeGenerationTests.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs index 5febe18aacdd4..3905b4b0e91be 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs @@ -956,7 +956,7 @@ public ImmutableArray ParseStatements(string statements) } } - return list.ToImmutable(); + return list.ToImmutableAndClear(); } public void Dispose() 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/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs index 415c50da62bb5..fdc91b04c1a0c 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs @@ -200,7 +200,7 @@ static ImmutableArray GetMergedChanges(TextChange? newLineEdit, Immu mergedChanges.Add(new TextChange(newTextChangeSpan, newTextChangeText)); } - return mergedChanges.ToImmutable(); + return mergedChanges.ToImmutableAndClear(); } } diff --git a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs index 3bd0f2c120203..e12e26279379c 100644 --- a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs +++ b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs @@ -763,7 +763,7 @@ private ImmutableArray TransferLeadingWhitespaceTrivia(IEnumerable n index++; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async ValueTask> UpdateParamTagsInLeadingTriviaAsync( 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/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/ConvertProgram/ConvertProgramTransform_ProgramMain.cs b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs index ec583a5ca0285..bd8b54b33a3dc 100644 --- a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs +++ b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_ProgramMain.cs @@ -146,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..4ac7c6f4365ef 100644 --- a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs +++ b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs @@ -231,7 +231,7 @@ private static ImmutableArray GetGlobalStatements( foreach (var statement in statements) globalStatements.Add(GlobalStatement(statement).WithAdditionalAnnotations(Formatter.Annotation)); - return globalStatements.ToImmutable(); + return globalStatements.ToImmutableAndClear(); } private static VariableDeclarationSyntax ConvertDeclaration( diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index 8c621ef831a49..c8a48212c6e61 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -67,6 +67,6 @@ protected override async Task> GetAdditionalReferencesA } } - return results.ToImmutable(); + return results.ToImmutableAndClear(); } } 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 366598ff22059..f097a8e1ad20c 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -408,7 +408,7 @@ private ImmutableArray MoveDeclarationOutFromMethodDefinition( result.AddRange(expressionStatements); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } /// diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs index b9573ad911765..5b0dfa7a74172 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.PostProcessor.cs @@ -108,7 +108,7 @@ private ImmutableArray MergeDeclarationStatementsWorker(Immutab if (map.Count > 0) result.AddRange(GetMergedDeclarationStatements(map)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private void AppendDeclarationStatementToMap( diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index 06b21c73514f7..e8a11332cf44e 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -99,7 +99,7 @@ protected override ImmutableArray GenerateTypeParameters(C list.Add(typeParameter); } - return list.ToImmutable(); + return list.ToImmutableAndClear(); } } @@ -158,7 +158,7 @@ protected override ImmutableArray DetermineTypeArguments(Cancellati } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } } diff --git a/src/Features/CSharp/Portable/LanguageServices/CSharpStructuralTypeDisplayService.cs b/src/Features/CSharp/Portable/LanguageServices/CSharpStructuralTypeDisplayService.cs index 3873059bed1dd..1c385d83565a4 100644 --- a/src/Features/CSharp/Portable/LanguageServices/CSharpStructuralTypeDisplayService.cs +++ b/src/Features/CSharp/Portable/LanguageServices/CSharpStructuralTypeDisplayService.cs @@ -57,6 +57,6 @@ protected override ImmutableArray 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 36af95476fd8a..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. diff --git a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs index 1f06dc9100fd9..ee7d28326b88c 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(); } } diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs index c0f41a8dd0b88..a7c7102ca66ba 100644 --- a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs @@ -100,7 +100,7 @@ private static ImmutableArray ConvertPropertyToMembers( cancellationToken)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static SyntaxNode GetSetMethod( diff --git a/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs index a5a18c484d87d..1ff99c4351f6d 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpForEachLoopSnippetProvider.cs @@ -113,7 +113,7 @@ protected override ImmutableArray GetPlaceHolderLocationsLis if (!ConstructedFromInlineExpression) arrayBuilder.Add(new SnippetPlaceholder(node.Expression.ToString(), node.Expression.SpanStart)); - return arrayBuilder.ToImmutable(); + return arrayBuilder.ToImmutableAndClear(); } protected override int GetTargetCaretPosition(ForEachStatementSyntax forEachStatement, SourceText sourceText) 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/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs index 7bfabecaf2835..6c5a7895fd584 100644 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs @@ -194,7 +194,7 @@ private static async Task> ComputeRefactoringsAsync( title)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static async Task UpdateDocumentAsync( 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/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..e2300a8a4fbee 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) @@ -192,6 +192,6 @@ public async Task> ComputeIntentAsync( results.Add(intent); } - return results.ToImmutable(); + return results.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs b/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs index c49e2d8fcdf05..647c0910067a8 100644 --- a/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs @@ -159,7 +159,7 @@ private ImmutableArray UpdateEmbeddedFileNames( result.Add(updated); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async Task> TryGetBannerAsync( diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 99094f7dcdd97..d25940ab6bd71 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( @@ -496,7 +496,7 @@ private static bool NotNull(SymbolReference reference) result.Add((diagnostic, fixes)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } 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/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 57508f95172f4..4850d08d7fd1b 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -602,7 +602,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/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 87850319e07e0..5984100dae2f1 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -746,7 +746,7 @@ protected ImmutableArray GetSeparators(SeparatedSyntaxList ar : CommaTokenWithElasticSpace()); } - return separators.ToImmutable(); + return separators.ToImmutableAndClear(); } protected virtual async Task> AddNewArgumentsToListAsync( @@ -1029,7 +1029,7 @@ protected ImmutableArray GetPermutedDocCommentTrivia(SyntaxNode no 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/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index da3355a3344b3..023fb637ee227 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -56,7 +56,7 @@ protected override async ValueTask> DetermineCascadedSym } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } protected override Task> DetermineDocumentsToSearchAsync( diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 2dfef354517ad..b36c3000c7745 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -360,7 +360,7 @@ private async Task ConfigureAsync(CancellationToken cancellationToken) builder.Add((option.Definition.ConfigName, optionValue, option.IsPerLanguage)); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } finally { 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/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.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..8ee217162cee2 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -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/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/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 6171d46223c1d..4bf683611ab5c 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -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/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/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index f3a276a0b9718..ace71a736df3c 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( @@ -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..e3472e4604f61 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( diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 58af5842ea690..0f58f0b5e126c 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -205,7 +205,7 @@ private static ImmutableArray ConvertSymbolsTo itemsBuilder.Add(item); } - return itemsBuilder.ToImmutable(); + return itemsBuilder.ToImmutableAndClear(); } private static bool ShouldIncludeInTargetTypedCompletion( 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/Scripting/AbstractLoadDirectiveCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs index 21b7f3f91b7d1..1d5bdf89688ce 100644 --- a/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs @@ -34,7 +34,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 680be79a82ff1..bf0374424e591 100644 --- a/src/Features/Core/Portable/Completion/Providers/Scripting/GlobalAssemblyCacheCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/Scripting/GlobalAssemblyCacheCompletionHelper.cs @@ -59,7 +59,7 @@ internal ImmutableArray GetItems(string directoryPath, Cancellat } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static IEnumerable GetAssemblyIdentities(string partialName) diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index 0990ee2947f07..2001d59c2c31c 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -155,7 +155,7 @@ public static async Task> GetSymbolsAsync(CompletionItem } } - return symbols.ToImmutable(); + return symbols.ToImmutableAndClear(); } return []; 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/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/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index e2881e3c26ea5..7125e80813e02 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -3809,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) diff --git a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs index 704b2934917e1..b5069bc9a549e 100644 --- a/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs +++ b/src/Features/Core/Portable/EditAndContinue/ActiveStatementsMap.cs @@ -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 071b40d5e7440..3b926fff1dbbf 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -739,7 +739,7 @@ public async ValueTask>> GetB activeStatementsInChangedDocuments.FreeValues(); Debug.Assert(spans.Count == documentIds.Length); - return spans.ToImmutable(); + return spans.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -816,7 +816,7 @@ public async ValueTask> GetAdjustedActiveSta } } - return adjustedMappedSpans.ToImmutable(); + return adjustedMappedSpans.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs index 69800c49f88d5..ea8381ceb81b2 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs @@ -123,6 +123,6 @@ public static ImmutableArray ToStringArray(this EditAndContinueCapabilit if (capabilities.HasFlag(EditAndContinueCapabilities.UpdateParameters)) builder.Add(nameof(EditAndContinueCapabilities.UpdateParameters)); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs index 90c463d540cf9..9e5ec07398c1c 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -158,7 +158,7 @@ private async Task> GetLatestUnmappedAct activeStatementSpansBuilder.Add(new ActiveStatementLineSpan(newMappedDocumentActiveSpan.Id, unmappedSpan)); } - return activeStatementSpansBuilder.ToImmutable(); + return activeStatementSpansBuilder.ToImmutableAndClear(); } private AsyncLazy GetDocumentAnalysisNoLock(Project baseProject, Document document, ImmutableArray activeStatementSpans) diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index fc558099aebaf..456cfaac1a639 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -181,7 +181,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) 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/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/RemoteEditAndContinueServiceProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs index 9e93e6c963a0f..aa5cd4388b2aa 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs @@ -215,7 +215,7 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D result.Add(diagnostic); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static Diagnostic RemapLocation(Document designTimeDocument, DiagnosticData data) 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/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/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/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 GetMethodParameters(DictionaryWhen false, variables whose data flow is not understood 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/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.cs b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs index c928cddf866f9..9a48f82069dc3 100644 --- a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs @@ -104,7 +104,7 @@ await ComputeRefactoringsAsync( results.AddIfNotNull(intentResult); } - return results.ToImmutable(); + return results.ToImmutableAndClear(); static async Task GetIntentProcessorResultAsync( Document priorDocument, CodeAction codeAction, IProgress progressTracker, CancellationToken cancellationToken) @@ -280,7 +280,7 @@ private ImmutableArray GetCodeActions(Document document, State state 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/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..27c3d1936f32a 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs @@ -113,7 +113,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.SignatureInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs index 9641bb4d73d38..425164cfe28bc 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs @@ -216,7 +216,7 @@ private async ValueTask> DetermineParametersAsy name: names[i].BestNameForParameter)); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } 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.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/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.GenerateNamedType.cs index a85787d4e4be3..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) diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.cs index 3a45f46b1aa00..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( 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 0a01da68140f6..f304e08c863d0 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs @@ -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 335f52732a0c0..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) diff --git a/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs b/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs index 86e490d30d974..919364162fef8 100644 --- a/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs +++ b/src/Features/Core/Portable/ImplementInterface/ImplementHelpers.cs @@ -62,7 +62,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_Helpers.cs b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs index 815e2bf225fc3..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( @@ -145,7 +145,7 @@ private async Task> GetInheritanceMarginIt 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/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/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/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs b/src/Features/Core/Portable/InvertIf/AbstractInvertIfCodeRefactoringProvider.cs index 09a60fe9f2cc8..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( diff --git a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs index a6fe4acda7d4e..f900b9c23ba54 100644 --- a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs +++ b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs @@ -55,7 +55,7 @@ private static ImmutableArray MassageDelegateParts( result.Add(part); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public StructuralTypeDisplayInfo GetTypeDisplayInfo( @@ -205,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/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/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs index 468ab3d47b214..bf2831289f9b8 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs @@ -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.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 730f4cac07699..5224895958359 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -239,7 +239,7 @@ private static async ValueTask> GetAdditionalProjectsW } result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index fc90beeb18284..85ef7d099d162 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -339,7 +339,7 @@ private ImmutableArray GetPriorityDocuments(ImmutableArray pr } result.RemoveDuplicates(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private async Task ProcessOrderedProjectsAsync( diff --git a/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs b/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs index c6e7b17c593ca..b01e635db8630 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs @@ -54,7 +54,7 @@ public ImmutableArray FindSourceDocuments(EntityHandle entityHan sourceDocuments.Add(new SourceDocument(filePath, hashAlgorithm, checksum, embeddedTextBytes, sourceLinkUrl)); } - return sourceDocuments.ToImmutable(); + return sourceDocuments.ToImmutableAndClear(); } 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/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs b/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs index 56ad59f47dbcb..d46f18607c8b7 100644 --- a/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ReplaceMethodWithProperty/ReplaceMethodWithPropertyCodeRefactoringProvider.cs @@ -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/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/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/SignatureHelp/AbstractSignatureHelpProvider.cs b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs index 9016af082906d..a4d8ee47c11db 100644 --- a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs +++ b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs @@ -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/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/SpellCheck/AbstractSpellCheckSpanService.cs b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs index fb4791044b82f..e2db8f7469e60 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(); } } 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/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..a6ef0fb053b04 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) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index 9c47cb263583d..e9e4b2bd13245 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -287,7 +287,7 @@ internal static async Task> GetDesignTimeDocumentsAsy } compileTimeFilePathsByProject.FreeValues(); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } } diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs index 5fdf919e4568d..12d3eb0980a70 100644 --- a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs +++ b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs @@ -98,7 +98,7 @@ private ImmutableArray GetExpressionsAndOperators( { using var _ = ArrayBuilder.GetInstance(out var result); AddExpressionsAndOperators(precedence, binaryExpr, result); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private void AddExpressionsAndOperators( diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs index 9cbcd65cfeeb5..5dcef3f9ee1ca 100644 --- a/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs +++ b/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs @@ -115,7 +115,7 @@ private ImmutableArray GetWrapEdits(bool align) } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } private ImmutableArray GetUnwrapEdits() @@ -129,7 +129,7 @@ private ImmutableArray GetUnwrapEdits() NoTrivia, _exprsAndOperators[i + 1])); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } } diff --git a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs index e6e6743f9622e..120c904887019 100644 --- a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs +++ b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs @@ -132,7 +132,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( 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/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs index 8ecd99cba7114..7237daa8f6cb9 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.cs @@ -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/Protocol/Features/CodeFixes/CodeFixService.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index f29feac5079e7..a50adc0b63f24 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -659,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, @@ -938,7 +938,7 @@ static ImmutableArray GetConfigurationFixProviders(Im builder.Add(languageKindAndFixersValue.Value); } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } } 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_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 118e5133c645e..af943468881a4 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -53,7 +53,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje } } - return diagnostics.ToImmutable(); + return diagnostics.ToImmutableAndClear(); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 75513230f0080..e5cd699d5d641 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -774,7 +774,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..b8ac33f20ddb6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -165,7 +165,7 @@ private static LSP.CodeAction[] GenerateCodeActions( } } - return nestedCodeActions.ToImmutable(); + return nestedCodeActions.ToImmutableAndClear(); } private static void AddLSPCodeActions( @@ -338,7 +338,7 @@ public static async Task> GetCodeActionsAsync( } } - return codeActions.ToImmutable(); + return codeActions.ToImmutableAndClear(); } /// 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/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index d39af98d94d7b..d45959627ed45 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -127,7 +127,7 @@ private static ImmutableArray GetTaskListDiagnosticSources( } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public static async ValueTask> GetDiagnosticSourcesAsync( @@ -144,7 +144,7 @@ public static async ValueTask> GetDiagnosticSo foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); - return result.ToImmutable(); + return result.ToImmutableAndClear(); async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) { 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/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/Workspaces/LspWorkspaceManager.cs b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs index 3c84bd75aeb67..ba01e7d11e979 100644 --- a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs +++ b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs @@ -285,7 +285,7 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) solutions.Add((workspace, lspSolution, isForked)); } - return solutions.ToImmutable(); + return solutions.ToImmutableAndClear(); async Task<(Solution Solution, bool IsForked)> GetLspSolutionForWorkspaceAsync(Workspace workspace, CancellationToken cancellationToken) { diff --git a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs index 9685f82743816..916f551d29251 100644 --- a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs +++ b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs @@ -152,7 +152,7 @@ internal static ImmutableArray GetUnmappedActiveStateme } activeStatements.Sort((x, y) => x.Statement.Id.Ordinal.CompareTo(y.Statement.Id.Ordinal)); - return activeStatements.ToImmutable(); + return activeStatements.ToImmutableAndClear(); } internal static ImmutableArray GetActiveStatementDebugInfos( diff --git a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs index c412b53e936d2..4c215eafae28e 100644 --- a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs +++ b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs @@ -88,7 +88,7 @@ static ImmutableArray SortDocumentSymbols( sortedDocumentSymbols.Add(ReplaceChildren(documentSymbol, sortedChildren)); } - return sortedDocumentSymbols.ToImmutable(); + return sortedDocumentSymbols.ToImmutableAndClear(); } static ImmutableArray Sort(ImmutableArray items, SortOption sortOption) diff --git a/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs b/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs index 333a63a045f85..cc9705260d5f7 100644 --- a/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs +++ b/src/VisualStudio/Core/Def/CodeLens/RemoteCodeLensReferencesService.cs @@ -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/DocumentOutline/DocumentOutlineViewModel_Utilities.cs b/src/VisualStudio/Core/Def/DocumentOutline/DocumentOutlineViewModel_Utilities.cs index 789dc0c6c2760..02b9a463842bd 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. @@ -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/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/Library/ObjectBrowser/AbstractListItemFactory.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs index cc6478b12b1c2..be5372c39863d 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) @@ -451,7 +451,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 +562,7 @@ private static ImmutableArray GetAccessibleTypeMembers(INamesp } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private static bool IncludeTypeMember(INamedTypeSymbol typeMember, IAssemblySymbol assemblySymbol) @@ -636,7 +636,7 @@ public ImmutableArray GetProjectListItems(Solution solution, str projectListItemBuilder.AddRange(referenceListItemBuilder); - return projectListItemBuilder.ToImmutable(); + return projectListItemBuilder.ToImmutableAndClear(); } public ImmutableArray GetReferenceListItems(ObjectListItem parentListItem, Compilation compilation) @@ -687,7 +687,7 @@ private static ImmutableArray GetAccessibleTypes(INamespaceSym } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } private ImmutableArray GetTypeListItems( @@ -718,7 +718,7 @@ private ImmutableArray GetTypeListItems( } } - return finalBuilder.ToImmutable(); + return finalBuilder.ToImmutableAndClear(); } public ImmutableArray GetTypeListItems(ObjectListItem parentListItem, Compilation compilation) diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 637147bbaa7c4..c2102cb3edb89 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -669,7 +669,7 @@ public ImmutableArray GetProjectsWithInstalledPackage(Solution solution } } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public bool CanShowManagePackagesDialog() diff --git a/src/VisualStudio/Core/Def/Progression/GraphProvider.cs b/src/VisualStudio/Core/Def/Progression/GraphProvider.cs index 04cb9c0d82a26..3a9ce9b855f0b 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphProvider.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphProvider.cs @@ -155,7 +155,7 @@ public static ImmutableArray GetGraphQueries(IGraphContext context) } } - return graphQueries.ToImmutable(); + return graphQueries.ToImmutableAndClear(); } public void BeginGetGraphData(IGraphContext context) 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/SymbolContainment.cs b/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs index 87dab244f14ca..0b298e0553561 100644 --- a/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs +++ b/src/VisualStudio/Core/Def/Progression/SymbolContainment.cs @@ -52,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/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/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/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index fe87d2561bea2..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) diff --git a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 4b7bfc4f1a93b..d5440f5d2d032 100644 --- a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -697,7 +697,7 @@ public ImmutableArray GetLiveErrors() allLiveErrorsBuilder.AddRange(errors); } - return allLiveErrorsBuilder.ToImmutable(); + return allLiveErrorsBuilder.ToImmutableAndClear(); // Local functions. IEnumerable GetProjectsWithErrors() @@ -728,7 +728,7 @@ public ImmutableArray GetLiveErrorsForProject(ProjectId projectI } } - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } public void AddErrors(DocumentId key, HashSet diagnostics) diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 45fdb1c714ec7..1a9747fc226a7 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -366,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) { 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 c3b3bdfc853cd..23f2b73784bed 100644 --- a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs +++ b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs @@ -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/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/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 53deba14cf6e3..5dfb1ce15e263 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -94,7 +94,7 @@ public async Task> GetProjectFileInfosAsync(Canc _loadedProject.ReevaluateIfNecessary(); - return results.ToImmutable(); + return results.ToImmutableAndClear(); } else { diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index c20e6b522a7a4..4b99843f78856 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) @@ -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() diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs index cb76d362524a5..8af7ec97ac916 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(); } /// diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs index 8599d0536cea8..949bea98a8ebb 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs @@ -235,7 +235,7 @@ public ImmutableArray GetAllDiagnostics() foreach (var data in _others) builder.AddRange(data); - return builder.ToImmutable(); + return builder.ToImmutableAndClear(); } public ImmutableArray GetDocumentDiagnostics(DocumentId documentId, AnalysisKind kind) diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 942a846fa8671..6128338a0a43c 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 { @@ -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/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/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..a5247d4839031 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -175,7 +175,7 @@ static ImmutableArray FindMatchingIdentifierTokensFromText( index = nextIndex; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } @@ -206,6 +206,6 @@ private static ImmutableArray GetConstructorInitializerTokensWorker } } - return initializers.ToImmutable(); + return initializers.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 706ea16052e72..094c5db7de444 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -41,7 +41,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 7383f6e71739a..277b0857651f7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -108,7 +108,7 @@ protected static async Task> FindDocumentsAsync( documents.Add(document); } - return documents.ToImmutable(); + return documents.ToImmutableAndClear(); } /// @@ -197,7 +197,7 @@ protected static async ValueTask> FindReferencesI } } - return locations.ToImmutable(); + return locations.ToImmutableAndClear(); } protected static FinderLocation CreateFinderLocation(FindReferencesDocumentState state, SyntaxToken token, CandidateReason reason, CancellationToken cancellationToken) @@ -300,7 +300,7 @@ private static async Task> FindReferencesThroughL } } - return allAliasReferences.ToImmutable(); + return allAliasReferences.ToImmutableAndClear(); } private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( @@ -324,7 +324,7 @@ private static async Task> FindReferencesThroughL } } - return allAliasReferences.ToImmutable(); + return allAliasReferences.ToImmutableAndClear(); } protected static Task> FindDocumentsWithPredicateAsync( @@ -381,7 +381,7 @@ protected static async Task> FindReferencesInDocu collectMatchingReferences(node, state, locations); } - return locations.ToImmutable(); + return locations.ToImmutableAndClear(); } return []; 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..d3c9275698895 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -90,7 +90,7 @@ protected static async ValueTask> FindReferencesI } } - return locations.ToImmutable(); + return locations.ToImmutableAndClear(); // 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..756ce8036d1fa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -59,7 +59,7 @@ ImmutableArray GetObjectCreationReferences(ImmutableArray> FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) @@ -140,7 +140,7 @@ await NamedTypeSymbolReferenceFinder.AddReferencesToTypeOrGlobalAliasToItAsync( result.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( methodSymbol, state, cancellationToken).ConfigureAwait(false)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } /// diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index bbf655f55c57d..7059fefdf72ab 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -56,7 +56,7 @@ protected sealed override async Task> DetermineDocument result.Add(document); } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 3dc76b005875f..74537c547319e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -72,7 +72,7 @@ protected override async Task> DetermineDocumentsToSear result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( project, documents, cancellationToken).ConfigureAwait(false)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } /// @@ -134,7 +134,7 @@ await AddReferencesToTypeOrGlobalAliasToItAsync( initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( namedType, state, cancellationToken).ConfigureAwait(false)); - return initialReferences.ToImmutable(); + return initialReferences.ToImmutableAndClear(); } internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index 1759897904691..dc0f2a36f79a3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -48,7 +48,7 @@ protected override async Task> DetermineDocumentsToSear var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); result.AddRange(documentsWithGlobalAttributes); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } protected override async ValueTask> FindReferencesInDocumentAsync( @@ -89,7 +89,7 @@ await AddNamedReferencesAsync( symbol, state, cancellationToken).ConfigureAwait(false)); } - return initialReferences.ToImmutable(); + return initialReferences.ToImmutableAndClear(); } /// diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index ef3cde6005202..c15af3ce4355e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -63,7 +63,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/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 05bdb9bf8bfa1..6d2acf8ebeb77 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -211,7 +211,7 @@ private static async Task> FindIndexerReferencesA candidateReason))); } - return locations.ToImmutable(); + return locations.ToImmutableAndClear(); } private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index dfc42e1bf5983..4678c6b5e4c22 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -44,7 +44,7 @@ public ImmutableArray GetReferencedSymbols() foreach (var (symbol, locations) in _symbolToLocations) result.Add(new ReferencedSymbol(symbol, locations.ToImmutableArray())); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } } 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/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 a0e0860b111e9..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) { 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/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/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 0fd2e6819501a..af8456252d02c 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IFindReferencesResultExtensions.cs @@ -139,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/ITypeSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs index 3dfc48994b3a6..6bef4e0d8324e 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs @@ -126,7 +126,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 b415cbcbe9421..ecce1282f978b 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions.cs @@ -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) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs index bb3d0623e47c8..fce1e3dc980a1 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs @@ -115,7 +115,7 @@ private static ImmutableArray CreateEqualsMethodStatements( if (containingType.IsRefLikeType) { statements.Add(factory.ReturnStatement(factory.FalseLiteralExpression())); - return statements.ToImmutable(); + return statements.ToImmutableAndClear(); } // Come up with a good name for the local variable we're going to compare against. @@ -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/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> MapSpansAsync( } } - return mappedFilePathAndTextChange.ToImmutable(); + return mappedFilePathAndTextChange.ToImmutableAndClear(); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs index cef12b7393db0..5d531395fb874 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs @@ -412,7 +412,7 @@ private async Task AnalyzeAsync( analyzerResults.Others))); } - return diagnostics.ToImmutable(); + return diagnostics.ToImmutableAndClear(); } 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/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 5d38cc493c2ab..323975f53f740 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -87,7 +87,7 @@ private static ImmutableArray Convert(ImmutableA foreach (var item in items) result.Add(SerializableSymbolAndProjectId.Dehydrate(solution, item, cancellationToken)); - return result.ToImmutable(); + return result.ToImmutableAndClear(); } public ValueTask> FindAllDeclarationsWithNormalQueryAsync( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 57362b6772187..8b2c346421d82 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -394,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/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/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/Services/SelectedMembers/AbstractSelectedMembers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SelectedMembers/AbstractSelectedMembers.cs index 1ed4de851a4fb..840b4cb27697f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SelectedMembers/AbstractSelectedMembers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SelectedMembers/AbstractSelectedMembers.cs @@ -92,7 +92,7 @@ private ImmutableArray GetMembersInSpan( AddSelectedMemberDeclarations(member, membersToKeep); } - return selectedMembers.ToImmutable(); + return selectedMembers.ToImmutableAndClear(); void AddAllMembers(TMemberDeclarationSyntax member) { 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/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs index b239c9f765cbc..22cd0e3cec9a8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs @@ -57,7 +57,7 @@ internal static ImmutableArray GetParameters( isFirstParam = false; } - return result.ToImmutable(); + return result.ToImmutableAndClear(); } internal static ParameterSyntax GetParameter(IParameterSymbol parameter, CSharpCodeGenerationContextInfo info, bool isExplicit, bool isFirstParam, bool seenOptional) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs index 419f016a12019..b15d8d2e2ff26 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs @@ -85,7 +85,7 @@ public static IEnumerable GetAllBaseListTypes(this TypeDeclarati baseListTypes.AddRange(baseTypes); } - return baseListTypes.ToImmutable(); + return baseListTypes.ToImmutableAndClear(); } } 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( From a4c1cd6d6fd2d4ea8f8802bed168cd60267a0811 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 19:10:52 -0700 Subject: [PATCH 0296/1047] Lint --- .../CodeFixes/Configuration/ConfigurationUpdater.cs | 8 ++++---- .../Portable/UnusedReferences/UnusedReferencesRemover.cs | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 3c1d7a5391de9..68c79177b07fc 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -392,10 +392,10 @@ internal static ImmutableArray GetCodeStyleOptionsForDiagnostic(Diagno { if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions(diagnostic.Id, project.Language, out var options)) { - return [.. (from option in options - where option.DefaultValue is ICodeStyleOption - orderby option.Definition.ConfigName - select option)]; + return [.. from option in options + where option.DefaultValue is ICodeStyleOption + orderby option.Definition.ConfigName + select option]; } return []; diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index f70b8941c613c..2d42b5bec4dfc 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -256,8 +256,7 @@ internal static ImmutableArray GetAllCompilationAssemblies(ReferenceInfo { var transitiveCompilationAssemblies = reference.Dependencies .SelectMany(dependency => GetAllCompilationAssemblies(dependency)); - return [.. reference.CompilationAssemblies -, .. transitiveCompilationAssemblies]; + return [.. reference.CompilationAssemblies, .. transitiveCompilationAssemblies]; } public static async Task UpdateReferencesAsync( From 9da4b2de55b1679fb1c04e67f8de13a7d315d2ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 19:19:54 -0700 Subject: [PATCH 0297/1047] lint --- .../ConvertForEachToLinqQuery/AbstractToMethodConverter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs index 54be9ac17f079..61f9cf84c86fe 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs @@ -124,8 +124,7 @@ void Convert(ExpressionSyntax replacingExpression, SyntaxNode nodeToRemoveIfFoll // Output: // return queryGenerated.ToList(); or return queryGenerated.Count(); replacingExpression = returnStatement.Expression; - leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) -, .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; + leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn), .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; editor.RemoveNode(nodeToRemoveIfFollowedByReturn); } else From 352ca30aa6c9f7b14e63362256eed5fb7256c805 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 21:27:43 -0700 Subject: [PATCH 0298/1047] revert --- .../UseCollectionExpressionForBuilderTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs index 40b463561fc63..bd0fbe4312df9 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForBuilderTests.cs @@ -77,7 +77,7 @@ ImmutableArray M() { {{pattern}} builder.Add(0); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -100,7 +100,7 @@ ImmutableArray M() { {{pattern}} builder.Add(0); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -123,7 +123,7 @@ ImmutableArray M() { {{pattern}} [|builder.Add(|]0); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -1256,7 +1256,7 @@ ImmutableArray M() // Leading [|builder.Add(|]0); // Trailing - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -1295,7 +1295,7 @@ ImmutableArray M() {{pattern}} [|builder.Add(|]1 + 2); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -1336,7 +1336,7 @@ ImmutableArray M() 2); [|builder.Add(|]3 + 4); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -1376,7 +1376,7 @@ ImmutableArray M() { using var _ = ArrayBuilder.GetInstance(10, 0, out var builder); builder.Add(0); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -1540,7 +1540,7 @@ IEnumerable M() { {{pattern}} [|builder.Add(|]0); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, @@ -1577,7 +1577,7 @@ IEnumerable M() { {{pattern}} builder.Add(0); - return builder.ToImmutableAndClear(); + return builder.ToImmutable(); } } """ + s_arrayBuilderApi, From 2dffdfb76bee111898a212e767338d9746b4ccc5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 21:32:27 -0700 Subject: [PATCH 0299/1047] Use collection expressoins --- .../UseCollectionExpressionForBuilderTests.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) 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) From 246cbebaa5d34637a49ff3f16fc10213bb83602b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 15:05:49 +0300 Subject: [PATCH 0300/1047] Add 'Make anonymous function static' feature --- .../Analyzers/CSharpAnalyzers.projitems | 1 + .../Analyzers/CSharpAnalyzersResources.resx | 6 + .../CSharpAnalyzerOptionsProvider.cs | 1 + ...onymousFunctionStaticDiagnosticAnalyzer.cs | 71 ++++ .../xlf/CSharpAnalyzersResources.cs.xlf | 10 + .../xlf/CSharpAnalyzersResources.de.xlf | 10 + .../xlf/CSharpAnalyzersResources.es.xlf | 10 + .../xlf/CSharpAnalyzersResources.fr.xlf | 10 + .../xlf/CSharpAnalyzersResources.it.xlf | 10 + .../xlf/CSharpAnalyzersResources.ja.xlf | 10 + .../xlf/CSharpAnalyzersResources.ko.xlf | 10 + .../xlf/CSharpAnalyzersResources.pl.xlf | 10 + .../xlf/CSharpAnalyzersResources.pt-BR.xlf | 10 + .../xlf/CSharpAnalyzersResources.ru.xlf | 10 + .../xlf/CSharpAnalyzersResources.tr.xlf | 10 + .../xlf/CSharpAnalyzersResources.zh-Hans.xlf | 10 + .../xlf/CSharpAnalyzersResources.zh-Hant.xlf | 10 + .../CodeFixes/CSharpCodeFixes.projitems | 1 + ...eAnonymousFunctionStaticCodeFixProvider.cs | 48 +++ .../Tests/CSharpAnalyzers.UnitTests.projitems | 1 + .../MakeAnonymousFunctionStaticTests.cs | 321 ++++++++++++++++++ .../Core/Analyzers/EnforceOnBuildValues.cs | 1 + .../Core/Analyzers/IDEDiagnosticIds.cs | 2 + .../PredefinedCodeFixProviderNames.cs | 1 + src/Compilers/Test/Core/Traits/Traits.cs | 1 + .../Impl/Options/Formatting/StyleViewModel.cs | 18 + .../Core/Def/ServicesVSResources.resx | 3 + .../Core/Def/xlf/ServicesVSResources.cs.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.de.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.es.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.fr.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.it.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.ja.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.ko.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.pl.xlf | 5 + .../Def/xlf/ServicesVSResources.pt-BR.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.ru.xlf | 5 + .../Core/Def/xlf/ServicesVSResources.tr.xlf | 5 + .../Def/xlf/ServicesVSResources.zh-Hans.xlf | 5 + .../Def/xlf/ServicesVSResources.zh-Hant.xlf | 5 + .../CodeStyle/CSharpCodeStyleOptions.cs | 6 +- .../CodeStyle/CSharpIdeCodeStyleOptions.cs | 2 +- 42 files changed, 677 insertions(+), 2 deletions(-) create mode 100644 src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs create mode 100644 src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs create mode 100644 src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs 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..737781ec44eeb --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.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 System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.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 (TryGetCaptures(anonymousFunction, context.SemanticModel, out var captures) && captures.IsEmpty) + { + context.ReportDiagnostic( + Diagnostic.Create( + Descriptor, + anonymousFunction.GetLocation())); + } + } + + private static bool TryGetCaptures(AnonymousFunctionExpressionSyntax anonymousFunction, SemanticModel semanticModel, out ImmutableArray captures) + { + var dataFlow = semanticModel.AnalyzeDataFlow(anonymousFunction); + if (dataFlow is null) + { + captures = default; + return false; + } + + captures = dataFlow.Captured; + return dataFlow.Succeeded; + } +} diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf index b20cc6f8ba857..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 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf index 5af1d97c90635..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 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf index 10d8ff8bbc94a..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' diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf index 240b308d2df21..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' diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf index 77f9052b7fd2a..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' diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf index 11fad2f8d6078..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' にする diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf index b46c592342a82..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'로 만들기 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf index 80aa29890c432..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” diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf index 2299d1557e947..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' diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf index 102c9ed8b71a9..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" diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf index 1d49d918214ce..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 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf index fb14afb9f4e93..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” diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf index 2460761cea7a4..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' diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems index b77968340c102..e8042dedf9e79 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems @@ -50,6 +50,7 @@ + diff --git a/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs new file mode 100644 index 0000000000000..4e592a23915ed --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.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.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)); + 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); + var modifiers = generator.GetModifiers(anonymousFunction); + + var fixedAnonymousFunction = generator.WithModifiers(anonymousFunction, modifiers.WithIsStatic(true)); + editor.ReplaceNode(anonymousFunction, fixedAnonymousFunction); + } + + return Task.CompletedTask; + } +} diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index ddd1745e0f5b3..1ce11ff7471ba 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -35,6 +35,7 @@ + diff --git a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs new file mode 100644 index 0000000000000..0c858b35baadc --- /dev/null +++ b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs @@ -0,0 +1,321 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .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({|IDE0310:{{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 => 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({|IDE0310:{{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({|IDE0310:{{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); + } +} diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 65cd9ad9ee9e1..1373929bacae6 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 = /*IDE0310*/ EnforceOnBuild.Recommended; /* EnforceOnBuild.WhenExplicitlyEnabled */ public const EnforceOnBuild RemoveUnnecessaryCast = /*IDE0004*/ EnforceOnBuild.WhenExplicitlyEnabled; // TODO: Move to 'Recommended' OR 'HighlyRecommended' bucket once performance problems are addressed: https://github.com/dotnet/roslyn/issues/43304 diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index fefb859cdeb09..6c4aec2037b4e 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 = "IDE0310"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; 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/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/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index fc8fe2951f882..ae782870da465 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -1548,6 +1548,23 @@ int fibonacci(int n) }} "; + private static readonly string s_preferStaticAnonymousFunction = $$""" + using System; + + //[ + // {{ServicesVSResources.Prefer_colon}} + Func f1 = static i => i * i; + Func f2 = static (i, j) => i * j; + Func f3 = static delegate (int i) { return i * i; }; + //] + //[ + // {{ServicesVSResources.Over_colon}} + Func f1 = i => i * i; + Func f2 = (i, j) => i * j; + Func f3 = delegate (int i) { return i * i; }; + //] + """; + #endregion #region New Line Preferences @@ -2292,6 +2309,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/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 0bef437805163..374efed02fbb5 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1936,4 +1936,7 @@ Additional information: {1} Semantic Search ({0}) + + Prefer static anonymous functions + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index f424498555d9c..870eb7f11ed19 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -1112,6 +1112,11 @@ Preferovat zjednodušenou interpolaci + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferovat statické místní funkce diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 19f0cb047d13c..1e025b0a8dd62 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -1112,6 +1112,11 @@ Vereinfachte Interpolation bevorzugen + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Statische lokale Funktionen bevorzugen diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index da4d5b345dd4f..01c6dad649d78 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -1112,6 +1112,11 @@ Preferir interpolación simplificada + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferir funciones locales estáticas diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index ef6cee9527c67..6ccd478502a98 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -1112,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 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index b51d4e69d9e0b..e20fa8c9d0a67 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -1112,6 +1112,11 @@ Preferire l'interpolazione semplificata + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferisci funzioni locali statiche diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 0b3a697dedd8f..a2906f99ae57e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -1112,6 +1112,11 @@ シンプルな補間を優先する + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 静的ローカル関数を優先する diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 7b0c7df05331a..98c46661fa844 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -1112,6 +1112,11 @@ 단순화된 보간 선호 + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 정적 로컬 함수 선호 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 2422d9e9c4db7..eff47657ecf77 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -1112,6 +1112,11 @@ Preferuj uproszczoną interpolację + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Preferuj statyczne funkcje lokalne diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index b430acb6694bc..dfa6a6fd905c5 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -1112,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 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index a1797952e12fc..cd9eb81db03a2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -1112,6 +1112,11 @@ Предпочитать упрощенную интерполяцию + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions Предпочитать статические локальные функции diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 8f20a68b057a6..e56267e6af81e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -1112,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 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 33b698875b618..4125ad72481bd 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -1112,6 +1112,11 @@ 首选简化内插 + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 首选静态本地函数 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 912b5db1a36cc..2abaa2a348650 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -1112,6 +1112,11 @@ 優先使用簡易動畫插補 + + Prefer static anonymous functions + Prefer static anonymous functions + + Prefer static local functions 優先使用靜態區域函式 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..3dab88bb89ec6 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; From a7b6434e5181155bda94bf0afed64dfb56838c43 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 13 Apr 2024 12:27:10 +0000 Subject: [PATCH 0301/1047] Update dependencies from https://github.com/dotnet/source-build-externals build 20240411.1 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 9.0.0-alpha.1.24208.1 -> To Version 9.0.0-alpha.1.24211.1 From 5ff2818362ebd61d3181dc1993b51a8a00fa1ccc Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 13 Apr 2024 12:28:24 +0000 Subject: [PATCH 0302/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240409.3 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520903 From 2d31ad5250c0e340c69fff8bb6519036cc113b0f Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 16:50:23 +0300 Subject: [PATCH 0303/1047] Fix editorconfig tests --- .../Core/Test/Options/CSharpEditorConfigGeneratorTests.vb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From da06c48d664bcfa5514caa9cd41b657a005a1894 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 16:53:40 +0300 Subject: [PATCH 0304/1047] Fix and refactor code cleanup test --- .../CSharpTest/Formatting/CodeCleanupTests.cs | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) 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 = """ From d6c68734a73e4e3995620134057ac918ab7e49c3 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 18:43:34 +0300 Subject: [PATCH 0305/1047] Refactor --- .../MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs index 737781ec44eeb..69727298a2de4 100644 --- a/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -47,7 +46,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) if (anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword)) return; - if (TryGetCaptures(anonymousFunction, context.SemanticModel, out var captures) && captures.IsEmpty) + if (CanAnonymousFunctionByMadeStatic(anonymousFunction, context.SemanticModel)) { context.ReportDiagnostic( Diagnostic.Create( @@ -56,16 +55,14 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) } } - private static bool TryGetCaptures(AnonymousFunctionExpressionSyntax anonymousFunction, SemanticModel semanticModel, out ImmutableArray captures) + private static bool CanAnonymousFunctionByMadeStatic(AnonymousFunctionExpressionSyntax anonymousFunction, SemanticModel semanticModel) { var dataFlow = semanticModel.AnalyzeDataFlow(anonymousFunction); - if (dataFlow is null) + if (dataFlow is not { Succeeded: true }) { - captures = default; return false; } - captures = dataFlow.Captured; - return dataFlow.Succeeded; + return dataFlow.Captured.IsEmpty; } } From ad6845fe19cbfe90acb383956e0e984eeb2a4157 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 18:55:06 +0300 Subject: [PATCH 0306/1047] Fix more tests --- .../IDEDiagnosticIDConfigurationTests.cs | 471 +++++++++--------- .../DataProvider/DataProviderTests.cs | 5 +- 2 files changed, 239 insertions(+), 237 deletions(-) diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index a26df2ed7e046..164f6ad6bb559 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% + # IDE0310 + dotnet_diagnostic.IDE0310.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"), + ("IDE0310", "csharp_prefer_static_anonymous_function", "true"), ("IDE1005", "csharp_style_conditional_delegate_call", "true"), ("IDE1006", null, null), ("IDE1007", null, null), diff --git a/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs b/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs index cc30ed940278c..14e4d2d695301 100644 --- a/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs +++ b/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs @@ -165,9 +165,10 @@ public void TestGettingCodeStyleSettingsProviderLanguageServiceAsync() settingsProvider.RegisterViewModel(model); var dataSnapShot = settingsProvider.GetCurrentDataSnapshot(); - // We don't support PreferredModifierOrder yet: + // We don't support PreferredModifierOrder and PreferStaticAnonymousFunction yet: var optionsWithUI = CSharpCodeStyleOptions.AllOptions - .Remove(CSharpCodeStyleOptions.PreferredModifierOrder); + .Remove(CSharpCodeStyleOptions.PreferredModifierOrder) + .Remove(CSharpCodeStyleOptions.PreferStaticAnonymousFunction); AssertEx.SetEqual(optionsWithUI, dataSnapShot.Select(setting => setting.Key.Option)); } From ebc7c0141f2447cb4921d41005f8a5628e8ae169 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 19:19:17 +0300 Subject: [PATCH 0307/1047] Fix nested case --- ...eAnonymousFunctionStaticCodeFixProvider.cs | 5 +- .../MakeAnonymousFunctionStaticTests.cs | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs index 4e592a23915ed..2c0071a9d9e8c 100644 --- a/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs @@ -37,10 +37,7 @@ protected override Task FixAllAsync(Document document, ImmutableArray generator.WithModifiers(node, generator.GetModifiers(node).WithIsStatic(true))); } return Task.CompletedTask; diff --git a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs index 0c858b35baadc..0b4b1e156aaf3 100644 --- a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs @@ -318,4 +318,92 @@ void N(Func f) await TestWithCSharp9Async(code, fixedCode); } + + [Fact] + public async Task TestNestedLambdasWithNoCaptures() + { + var code = """ + using System; + + class C + { + void M() + { + N({|IDE0310:() => + { + Action a = {|IDE0310:() => { }|}; + }|}); + } + + 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({|IDE0310:delegate () + { + Action a = {|IDE0310: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); + } } From 622c8b83b7ae62fedde50549bd350c57f2fba67d Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 19:26:08 +0300 Subject: [PATCH 0308/1047] Make example semantically correct --- .../CSharp/Impl/Options/Formatting/StyleViewModel.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index ae782870da465..166ffa7af88cc 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -1550,18 +1550,17 @@ int fibonacci(int n) private static readonly string s_preferStaticAnonymousFunction = $$""" using System; - //[ // {{ServicesVSResources.Prefer_colon}} Func f1 = static i => i * i; - Func f2 = static (i, j) => i * j; - Func f3 = static delegate (int i) { return i * i; }; + Func f2 = static delegate (int i) { return i * i; }; + Func f3 = static (i, j) => i * j; //] //[ // {{ServicesVSResources.Over_colon}} Func f1 = i => i * i; - Func f2 = (i, j) => i * j; - Func f3 = delegate (int i) { return i * i; }; + Func f2 = delegate (int i) { return i * i; }; + Func f3 = (i, j) => i * j; //] """; From ac8070af72889d7d9a67808a8aa9817a0fa20057 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:44:41 -0700 Subject: [PATCH 0309/1047] Move to better type --- src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs index 3905b4b0e91be..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.ToImmutableAndClear(); + return list.MoveToImmutable(); } public void Dispose() From c42c71373653db87901c4ac41c37cf9decb07a1f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:45:28 -0700 Subject: [PATCH 0310/1047] Move to better type --- ...ConstructorParametersFromMembersCodeRefactoringProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs index e2300a8a4fbee..583adc338a0e3 100644 --- a/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/AddConstructorParametersFromMembers/AddConstructorParametersFromMembersCodeRefactoringProvider.cs @@ -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.ToImmutableAndClear(); + return results.MoveToImmutable(); } } From f40ad02d6f596448704983ca994db27c8ea5347e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:46:40 -0700 Subject: [PATCH 0311/1047] Move to better type --- .../Portable/AddImport/AbstractAddImportFeatureService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index d25940ab6bd71..ca3f102bcfc9a 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -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.ToImmutableAndClear(); + return result.MoveToImmutable(); } public async Task> GetUniqueFixesAsync( From bbeb8e340c45494a64e3e7e4610dc7384f6b6e2f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:47:31 -0700 Subject: [PATCH 0312/1047] Move to better type --- .../ChangeSignature/AbstractChangeSignatureService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 5984100dae2f1..852b345190385 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -737,16 +737,17 @@ 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; + var separators = new FixedSizeArrayBuilder(count); - for (var i = 0; i < arguments.SeparatorCount - numSeparatorsToSkip; i++) + for (var i = 0; i < count; i++) { separators.Add(i < arguments.SeparatorCount ? arguments.GetSeparator(i) : CommaTokenWithElasticSpace()); } - return separators.ToImmutableAndClear(); + return separators.MoveToImmutable(); } protected virtual async Task> AddNewArgumentsToListAsync( From d5758710f1530e095ccd3583ad856dd06da67dc7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:48:26 -0700 Subject: [PATCH 0313/1047] Move to better type --- .../Configuration/ConfigurationUpdater.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index b36c3000c7745..a9d1b96663ade 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -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.ToImmutableAndClear(); - } - finally - { - builder.Free(); + var optionValue = option.Definition.Serializer.Serialize(option.DefaultValue); + builder.Add((option.Definition.ConfigName, optionValue, option.IsPerLanguage)); } + + return builder.MoveToImmutable(); } return []; From 4bd15f89e17360691fa8581273e9e5889296328f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:49:22 -0700 Subject: [PATCH 0314/1047] Move to better type --- .../ExtensionMethodImportCompletionHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 0f58f0b5e126c..d63813efdca06 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -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.ToImmutableAndClear(); + return itemsBuilder.MoveToImmutable(); } private static bool ShouldIncludeInTargetTypedCompletion( From a1d8a1e4dbd1d9d65d4d32fc365f9a4a496b0e23 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:50:07 -0700 Subject: [PATCH 0315/1047] Move to better type --- .../Scripting/AbstractLoadDirectiveCompletionProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Scripting/AbstractLoadDirectiveCompletionProvider.cs index 1d5bdf89688ce..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) { From 86a73a4097893e4798569435e48695ba745b13f3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:50:55 -0700 Subject: [PATCH 0316/1047] Move to better type --- .../Remote/RemoteEditAndContinueServiceProxy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs index aa5cd4388b2aa..dcb61c00ee82b 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs @@ -193,7 +193,7 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D var project = document.Project; - using var _ = ArrayBuilder.GetInstance(out var result); + var result = new FixedSizeArrayBuilder(diagnosticData.Value.Length); foreach (var data in diagnosticData.Value) { Debug.Assert(data.DataLocation != null); @@ -215,7 +215,7 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D result.Add(diagnostic); } - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } private static Diagnostic RemapLocation(Document designTimeDocument, DiagnosticData data) From f2e6fadfd2d7fc0595fb0dc9123ac83d99a97f9d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 09:59:15 -0700 Subject: [PATCH 0317/1047] Move to better type --- ...actGenerateConstructorFromMembersCodeRefactoringProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs index 9a48f82069dc3..6335e33decd19 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; @@ -274,7 +275,7 @@ public async Task> GenerateConstructorFromMembersAsyn private ImmutableArray GetCodeActions(Document document, State state, bool addNullChecks, CleanCodeGenerationOptionsProvider fallbackOptions) { - using var _ = ArrayBuilder.GetInstance(out var result); + var result = TemporaryArray.Empty; result.Add(new FieldDelegatingCodeAction(this, document, state, addNullChecks, fallbackOptions)); if (state.DelegatedConstructor != null) From 89acd32ea3303b7390a8b2604c2e55d4b336f9ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:00:02 -0700 Subject: [PATCH 0318/1047] Move to better type --- ...actGenerateConstructorFromMembersCodeRefactoringProvider.cs | 2 +- .../GenerateConstructor/AbstractGenerateConstructorService.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs index 6335e33decd19..842c90a3eb6e8 100644 --- a/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateConstructorFromMembers/AbstractGenerateConstructorFromMembersCodeRefactoringProvider.cs @@ -275,7 +275,7 @@ public async Task> GenerateConstructorFromMembersAsyn private ImmutableArray GetCodeActions(Document document, State state, bool addNullChecks, CleanCodeGenerationOptionsProvider fallbackOptions) { - var result = TemporaryArray.Empty; + using var result = TemporaryArray.Empty; result.Add(new FieldDelegatingCodeAction(this, document, state, addNullChecks, fallbackOptions)); if (state.DelegatedConstructor != null) diff --git a/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs b/src/Features/Core/Portable/GenerateMember/GenerateConstructor/AbstractGenerateConstructorService.cs index 27c3d1936f32a..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) From 288e932bdb155fe28203b45ccfacdae1baaa2d0c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:01:03 -0700 Subject: [PATCH 0319/1047] Move to better type --- ...bstractGenerateParameterizedMemberService.SignatureInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.SignatureInfo.cs index 425164cfe28bc..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.ToImmutableAndClear(); + return result.MoveToImmutable(); } private Accessibility DetermineAccessibility(bool isAbstract) From bbcac94e166e0cd87d36b41a387278e7d00d1728 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:02:23 -0700 Subject: [PATCH 0320/1047] Move to better type --- .../Portable/PdbSourceDocument/DocumentDebugInfoReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs b/src/Features/Core/Portable/PdbSourceDocument/DocumentDebugInfoReader.cs index b01e635db8630..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.ToImmutableAndClear(); + return sourceDocuments.MoveToImmutable(); } private string? TryGetSourceLinkUrl(DocumentHandle handle) From df879550ed5a94652d72ec9ef840b78a41afc5ff Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:03:21 -0700 Subject: [PATCH 0321/1047] Move to better type --- .../BinaryExpression/BinaryExpressionCodeActionComputer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs index 5dcef3f9ee1ca..34d657cdc1884 100644 --- a/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs +++ b/src/Features/Core/Portable/Wrapping/BinaryExpression/BinaryExpressionCodeActionComputer.cs @@ -120,16 +120,17 @@ private ImmutableArray GetWrapEdits(bool align) 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.ToImmutableAndClear(); + return result.MoveToImmutable(); } } } From bac93e11a727bd2bdbd9abcb5dc978a0cb6902ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:04:00 -0700 Subject: [PATCH 0322/1047] Move to better type --- .../AbstractCurlyBraceOrBracketCompletionService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs index fdc91b04c1a0c..f01fef914a806 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs @@ -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.ToImmutableAndClear(); + return mergedChanges.MoveToImmutable(); } } From c3d51d7f33218e133ab4080963be31aa74653493 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:04:38 -0700 Subject: [PATCH 0323/1047] Move to better type --- .../ConvertProgramTransform_TopLevelStatements.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs b/src/Features/CSharp/Portable/ConvertProgram/ConvertProgramTransform_TopLevelStatements.cs index 4ac7c6f4365ef..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.ToImmutableAndClear(); + return globalStatements.MoveToImmutable(); } private static VariableDeclarationSyntax ConvertDeclaration( From 437f2479646bc177d72b1d27c45e799b9d6f2426 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:06:08 -0700 Subject: [PATCH 0324/1047] Move to better type --- .../CSharpGenerateParameterizedMemberService.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index e8a11332cf44e..a2afc24618db4 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -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.ToImmutableAndClear(); + return list.MoveToImmutable(); } } From 035fd297979846f29f5f3f3dfa5f594ed17456c1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:07:11 -0700 Subject: [PATCH 0325/1047] Move to better type --- .../CSharpGenerateParameterizedMemberService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index a2afc24618db4..6bab88b6f96d8 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -148,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.ToImmutableAndClear(); + return result.MoveToImmutable(); } } } From 5ea35046c246cd907066f1947da0f2c4491e6716 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:07:52 -0700 Subject: [PATCH 0326/1047] Move to better type --- .../CSharpReplacePropertyWithMethodsService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs index a7c7102ca66ba..e74409937af67 100644 --- a/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs +++ b/src/Features/CSharp/Portable/ReplacePropertyWithMethods/CSharpReplacePropertyWithMethodsService.cs @@ -16,8 +16,8 @@ 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; @@ -72,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) { From a360d67f9e23f8239d6bfe75d3351ff1956b0006 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:08:25 -0700 Subject: [PATCH 0327/1047] Move to better type --- .../UseExpressionBodyForLambdaCodeRefactoringProvider.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs index 6c5a7895fd584..371201965e981 100644 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -168,13 +169,11 @@ private static async Task> 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(); From 3c8bcaa37b0e161e1db5ce98aff9dad781b20e89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:09:07 -0700 Subject: [PATCH 0328/1047] Move to better type --- .../Protocol/Features/CodeFixes/CodeFixService.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index a50adc0b63f24..52f031d5db3a5 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -931,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.ToImmutableAndClear(); + return builder.MoveToImmutable(); } } From c72a167007bc7bfad4593e0c94d820f096c846a3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:11:54 -0700 Subject: [PATCH 0329/1047] Move to better type --- .../LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs index ba01e7d11e979..289f609e7df67 100644 --- a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs +++ b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs @@ -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.ToImmutableAndClear(); + return solutions.MoveToImmutable(); async Task<(Solution Solution, bool IsForked)> GetLspSolutionForWorkspaceAsync(Workspace workspace, CancellationToken cancellationToken) { From e9c8ff5f236117ec7822a6fe728b13c1ba006091 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:13:23 -0700 Subject: [PATCH 0330/1047] Move to better type --- .../CSharp/Test/DocumentOutline/DocumentOutlineTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs b/src/VisualStudio/CSharp/Test/DocumentOutline/DocumentOutlineTests.cs index 4c215eafae28e..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.ToImmutableAndClear(); + return sortedDocumentSymbols.MoveToImmutable(); } static ImmutableArray Sort(ImmutableArray items, SortOption sortOption) From 3e7c13e30cf3bfd4f3e7d4cbdbd47387bbc31e75 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:13:56 -0700 Subject: [PATCH 0331/1047] Move to better type --- .../Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index 4b99843f78856..19c234bb6c285 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -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 Date: Sat, 13 Apr 2024 10:14:45 -0700 Subject: [PATCH 0332/1047] Move to better type --- .../Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 5dfb1ce15e263..4fa6436771ed5 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -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.ToImmutableAndClear(); + return results.MoveToImmutable(); } else { From e2f73172ee5a062d97fccd80abb46731249d6ee8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:15:18 -0700 Subject: [PATCH 0333/1047] Move to better type --- .../Core/Portable/FindSymbols/StreamingProgressCollector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 4678c6b5e4c22..9c62cd2c19139 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -40,11 +40,11 @@ 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())); - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } } From 74e09007f719f7683c12c38164aaa33377acf4ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:17:13 -0700 Subject: [PATCH 0334/1047] Move to better type --- .../SyntaxGeneratorExtensions_CreateEqualsMethod.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SyntaxGeneratorExtensions_CreateEqualsMethod.cs index fce1e3dc980a1..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.ToImmutableAndClear(); + 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: // From d5a4dd29412dc3b532ea369e18b1a375de22c195 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:17:48 -0700 Subject: [PATCH 0335/1047] Move to better type --- .../Services/DiagnosticAnalyzer/DiagnosticComputer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/DiagnosticComputer.cs index 5d531395fb874..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.ToImmutableAndClear(); + return diagnostics.MoveToImmutable(); } private static ImmutableArray<(string analyzerId, AnalyzerTelemetryInfo)> GetTelemetryInfo( From 56419f29ed6a4350303983ec153e424cc705c165 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:18:26 -0700 Subject: [PATCH 0336/1047] Move to better type --- .../Services/SymbolFinder/RemoteSymbolFinderService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 323975f53f740..43be04c3b336c 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.ToImmutableAndClear(); + return result.MoveToImmutable(); } public ValueTask> FindAllDeclarationsWithNormalQueryAsync( From e28c5bb60e2b0161f5a995cc3e86e86231c9db21 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:19:02 -0700 Subject: [PATCH 0337/1047] Move to better type --- .../Workspace/CSharp/CodeGeneration/ParameterGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs index 22cd0e3cec9a8..dde1cec02d278 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ParameterGenerator.cs @@ -45,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); @@ -57,7 +57,7 @@ internal static ImmutableArray GetParameters( isFirstParam = false; } - return result.ToImmutableAndClear(); + return result.MoveToImmutable(); } internal static ParameterSyntax GetParameter(IParameterSymbol parameter, CSharpCodeGenerationContextInfo info, bool isExplicit, bool isFirstParam, bool seenOptional) From 6241a0c2047a71533e32c034752b3433616b16bc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 10:20:06 -0700 Subject: [PATCH 0338/1047] Move to better type --- .../AbstractAddFileBannerCodeRefactoringProvider.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs b/src/Features/Core/Portable/AddFileBanner/AbstractAddFileBannerCodeRefactoringProvider.cs index 647c0910067a8..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.ToImmutableAndClear(); + return result.MoveToImmutable(); } private async Task> TryGetBannerAsync( From 7757059c745410433bef77c383d1bc3496f3ed2c Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 20:35:04 +0300 Subject: [PATCH 0339/1047] Simplify --- ...MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs index 69727298a2de4..d1edd47f80269 100644 --- a/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticDiagnosticAnalyzer.cs @@ -46,7 +46,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) if (anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword)) return; - if (CanAnonymousFunctionByMadeStatic(anonymousFunction, context.SemanticModel)) + if (context.SemanticModel.AnalyzeDataFlow(anonymousFunction) is { Succeeded: true, Captured.IsEmpty: true }) { context.ReportDiagnostic( Diagnostic.Create( @@ -54,15 +54,4 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) anonymousFunction.GetLocation())); } } - - private static bool CanAnonymousFunctionByMadeStatic(AnonymousFunctionExpressionSyntax anonymousFunction, SemanticModel semanticModel) - { - var dataFlow = semanticModel.AnalyzeDataFlow(anonymousFunction); - if (dataFlow is not { Succeeded: true }) - { - return false; - } - - return dataFlow.Captured.IsEmpty; - } } From 5cf909c7b1627522a4ccc2aae8e1094a2284f82f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 11:22:35 -0700 Subject: [PATCH 0340/1047] Mix --- .../Portable/ChangeSignature/AbstractChangeSignatureService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 852b345190385..e6f5785a4a2f5 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -738,6 +738,9 @@ protected static int GetParameterIndex(SeparatedSyntaxList paramet protected ImmutableArray GetSeparators(SeparatedSyntaxList arguments, int numSeparatorsToSkip) where T : SyntaxNode { var count = arguments.SeparatorCount - numSeparatorsToSkip; + if (count < 0) + return []; + var separators = new FixedSizeArrayBuilder(count); for (var i = 0; i < count; i++) From 8c74d24bdb28c9c72b97005049f4088388cdfb87 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 11:29:20 -0700 Subject: [PATCH 0341/1047] Remove explicit free --- .../ChangeSignature/AbstractChangeSignatureService.cs | 4 +--- .../CodeActionsLegacy/AbstractCodeActionTest_NoEditor.cs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index e6f5785a4a2f5..99322262f53c0 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -1013,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]); @@ -1031,8 +1031,6 @@ protected ImmutableArray GetPermutedDocCommentTrivia(SyntaxNode no updatedLeadingTrivia.Add(newTrivia); } - extraNodeList.Free(); - return updatedLeadingTrivia.ToImmutableAndClear(); } 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; } From 305bb8ebc17177db26650d116c936ed48d5e4c52 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 11:34:43 -0700 Subject: [PATCH 0342/1047] Remove explicit free --- .../Core/Extensions/SyntaxTokenListExtensions.cs | 7 ------- .../CSharp/CodeGeneration/ConstructorGenerator.cs | 6 ++---- .../CSharp/CodeGeneration/EventGenerator.cs | 6 +++--- .../CSharp/CodeGeneration/FieldGenerator.cs | 12 ++---------- .../CSharp/CodeGeneration/MethodGenerator.cs | 10 +++------- .../CSharp/CodeGeneration/NamedTypeGenerator.cs | 14 +++----------- .../CSharp/CodeGeneration/PropertyGenerator.cs | 6 +++--- 7 files changed, 16 insertions(+), 45 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs index 13f8933d4a916..78f77b2d49258 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs @@ -11,11 +11,4 @@ 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/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs index 54057ca72c0df..1120246f9ee1f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs @@ -117,7 +117,7 @@ private static BlockSyntax GenerateBlock( private static SyntaxTokenList GenerateModifiers(IMethodSymbol constructor, CSharpCodeGenerationContextInfo info) { - var tokens = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var tokens); if (constructor.IsStatic) { @@ -129,10 +129,8 @@ private static SyntaxTokenList GenerateModifiers(IMethodSymbol constructor, CSha } if (CodeGenerationConstructorInfo.GetIsUnsafe(constructor)) - { tokens.Add(UnsafeKeyword); - } - return tokens.ToSyntaxTokenListAndFree(); + return tokens.ToSyntaxTokenList(); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs index 3a1bc0601ce51..8f39cb352952a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs @@ -192,7 +192,7 @@ 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()) @@ -216,7 +216,7 @@ private static SyntaxTokenList GenerateModifiers( } else { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(@event.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(@event.DeclaredAccessibility, tokens, info, Accessibility.Private); if (@event.IsStatic) tokens.Add(StaticKeyword); @@ -240,6 +240,6 @@ private static SyntaxTokenList GenerateModifiers( if (CodeGenerationEventInfo.GetIsUnsafe(@event)) tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenListAndFree(); + return tokens.ToSyntaxTokenList(); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs index b20a5bf29fa0a..5c62a09916f54 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs @@ -121,7 +121,7 @@ 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); if (field.IsConst) @@ -131,26 +131,18 @@ private static SyntaxTokenList GenerateModifiers(IFieldSymbol field, CSharpCodeG else { if (field.IsStatic) - { tokens.Add(StaticKeyword); - } if (field.IsReadOnly) - { tokens.Add(ReadOnlyKeyword); - } if (field.IsRequired) - { tokens.Add(RequiredKeyword); - } } if (CodeGenerationFieldInfo.GetIsUnsafe(field)) - { tokens.Add(UnsafeKeyword); - } - return tokens.ToSyntaxTokenListAndFree(); + return tokens.ToSyntaxTokenList(); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs index 4e76fddfac39e..4cbf9e2c74082 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs @@ -254,7 +254,7 @@ 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()) @@ -282,7 +282,7 @@ private static SyntaxTokenList GenerateModifiers( 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(StaticKeyword); @@ -323,16 +323,12 @@ private static SyntaxTokenList GenerateModifiers( if (destination != CodeGenerationDestination.InterfaceType) { if (CodeGenerationMethodInfo.GetIsAsyncMethod(method)) - { tokens.Add(AsyncKeyword); - } } if (CodeGenerationMethodInfo.GetIsPartial(method) && method.IsAsync) - { tokens.Add(PartialKeyword); - } - return tokens.ToSyntaxTokenListAndFree(); + return tokens.ToSyntaxTokenList(); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs index eaa4ba4e4ab0d..b806fe2c8933e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs @@ -268,14 +268,14 @@ 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 { @@ -291,28 +291,20 @@ private static SyntaxTokenList GenerateModifiers( if (namedType.TypeKind == TypeKind.Class) { if (namedType.IsAbstract) - { tokens.Add(AbstractKeyword); - } if (namedType.IsSealed) - { tokens.Add(SealedKeyword); - } } } if (namedType.IsReadOnly) - { tokens.Add(ReadOnlyKeyword); - } if (namedType.IsRefLikeType) - { tokens.Add(RefKeyword); - } - return tokens.ToSyntaxTokenListAndFree(); + return tokens.ToSyntaxTokenList(); } private static TypeParameterListSyntax? GenerateTypeParameterList( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs index 9ce098e1446f4..388fb6fc21752 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs @@ -327,12 +327,12 @@ 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; @@ -341,7 +341,7 @@ private static SyntaxTokenList GenerateAccessorModifiers( modifiers.Add(ReadOnlyKeyword); } - return modifiers.ToSyntaxTokenListAndFree(); + return modifiers.ToSyntaxTokenList(); } private static SyntaxTokenList GenerateModifiers( From 07896646a8042c5cf6b7e78d3462d60548312649 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 11:40:29 -0700 Subject: [PATCH 0343/1047] Simplify --- .../Organizing/Organizers/ModifiersOrganizer.cs | 2 +- .../Compiler/Core/CompilerExtensions.projitems | 1 - .../Core/Extensions/SyntaxTokenListExtensions.cs | 14 -------------- .../CodeGeneration/CSharpCodeGenerationService.cs | 6 ++---- .../CSharp/CodeGeneration/ConstructorGenerator.cs | 9 ++++----- .../CSharp/CodeGeneration/EventGenerator.cs | 2 +- .../CSharp/CodeGeneration/FieldGenerator.cs | 8 ++++---- .../CSharp/CodeGeneration/MethodGenerator.cs | 2 +- .../CSharp/CodeGeneration/NamedTypeGenerator.cs | 2 +- .../CSharp/CodeGeneration/OperatorGenerator.cs | 4 +--- .../CSharp/CodeGeneration/PropertyGenerator.cs | 8 +++----- .../AbstractCodeGenerationService.cs | 6 +----- 12 files changed, 19 insertions(+), 45 deletions(-) delete mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs diff --git a/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs b/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs index 1e8336d3e1885..743e7b08a9f4e 100644 --- a/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs +++ b/src/Features/CSharp/Portable/Organizing/Organizers/ModifiersOrganizer.cs @@ -27,7 +27,7 @@ public static SyntaxTokenList Organize(SyntaxTokenList modifiers) { finalList[0] = finalList[0].WithLeadingTrivia(leadingTrivia); - return finalList.ToSyntaxTokenList(); + return [.. finalList]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index ac76082e5e8e3..87ccfaddde658 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -533,7 +533,6 @@ - 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 78f77b2d49258..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxTokenListExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using Microsoft.CodeAnalysis.PooledObjects; - -namespace Microsoft.CodeAnalysis.Shared.Extensions; - -internal static class SyntaxTokenListExtensions -{ - public static SyntaxTokenList ToSyntaxTokenList(this IEnumerable tokens) - => new(tokens); -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs index aaeae082f5d20..d70dc269c854c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs @@ -729,14 +729,12 @@ private static TDeclarationNode UpdateDeclarationModifiers(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 1120246f9ee1f..2b9e2e42986d6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/ConstructorGenerator.cs @@ -10,12 +10,11 @@ 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; @@ -125,12 +124,12 @@ private static SyntaxTokenList GenerateModifiers(IMethodSymbol constructor, CSha } else { - CSharpCodeGenerationHelpers.AddAccessibilityModifiers(constructor.DeclaredAccessibility, tokens, info, Accessibility.Private); + AddAccessibilityModifiers(constructor.DeclaredAccessibility, tokens, info, Accessibility.Private); } if (CodeGenerationConstructorInfo.GetIsUnsafe(constructor)) tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs index 8f39cb352952a..6891dcb45c718 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/EventGenerator.cs @@ -240,6 +240,6 @@ private static SyntaxTokenList GenerateModifiers( if (CodeGenerationEventInfo.GetIsUnsafe(@event)) tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs index 5c62a09916f54..f6950b2e9d564 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/FieldGenerator.cs @@ -11,11 +11,11 @@ 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; @@ -123,7 +123,7 @@ private static SyntaxTokenList GenerateModifiers(IFieldSymbol field, CSharpCodeG { 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(ConstKeyword); @@ -143,6 +143,6 @@ private static SyntaxTokenList GenerateModifiers(IFieldSymbol field, CSharpCodeG if (CodeGenerationFieldInfo.GetIsUnsafe(field)) tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs index 4cbf9e2c74082..96beaea6d97c0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/MethodGenerator.cs @@ -329,6 +329,6 @@ private static SyntaxTokenList GenerateModifiers( if (CodeGenerationMethodInfo.GetIsPartial(method) && method.IsAsync) tokens.Add(PartialKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs index b806fe2c8933e..810a767498d1b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/NamedTypeGenerator.cs @@ -304,7 +304,7 @@ private static SyntaxTokenList GenerateModifiers( if (namedType.IsRefLikeType) tokens.Add(RefKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } private static TypeParameterListSyntax? GenerateTypeParameterList( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs index f9cb70919cb60..1e0c1fd58a445 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/OperatorGenerator.cs @@ -119,10 +119,8 @@ private static SyntaxTokenList GenerateModifiers(IMethodSymbol method, CodeGener tokens.Add(StaticKeyword); if (method.IsAbstract) - { tokens.Add(AbstractKeyword); - } - return tokens.ToImmutableAndClear().ToSyntaxTokenList(); + return [.. tokens.ToImmutableAndClear()]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs index 388fb6fc21752..fb5130ab4ee32 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/PropertyGenerator.cs @@ -337,11 +337,9 @@ private static SyntaxTokenList GenerateAccessorModifiers( var hasNonReadOnlyAccessor = property.GetMethod?.IsReadOnly == false || property.SetMethod?.IsReadOnly == false; if (hasNonReadOnlyAccessor && accessor.IsReadOnly) - { modifiers.Add(ReadOnlyKeyword); - } - return modifiers.ToSyntaxTokenList(); + return [.. modifiers]; } private static SyntaxTokenList GenerateModifiers( @@ -370,7 +368,7 @@ private static SyntaxTokenList GenerateModifiers( } 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(StaticKeyword); @@ -404,6 +402,6 @@ private static SyntaxTokenList GenerateModifiers( if (CodeGenerationPropertyInfo.GetIsUnsafe(property)) tokens.Add(UnsafeKeyword); - return tokens.ToSyntaxTokenList(); + return [.. tokens]; } } 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]; } } From 73171b57bfe8a9aaeda2e4c672eb99e6f61488b5 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 21:54:29 +0300 Subject: [PATCH 0344/1047] Bump diagnostic ID --- .../MakeAnonymousFunctionStaticTests.cs | 14 +++++++------- .../Core/Analyzers/EnforceOnBuildValues.cs | 2 +- src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs | 2 +- .../IDEDiagnosticIDConfigurationTests.cs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs index 0b4b1e156aaf3..f3f98bfccfc09 100644 --- a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs @@ -129,7 +129,7 @@ class C { void M() { - N({|IDE0310:{{anonymousFunctionSyntax}}|}); + N({|IDE0320:{{anonymousFunctionSyntax}}|}); } void N(Action a) @@ -222,7 +222,7 @@ class C { void M(int i) { - N({|IDE0310:{{anonymousFunctionSyntax}}|}); + N({|IDE0320:{{anonymousFunctionSyntax}}|}); } void N(Func f) @@ -290,7 +290,7 @@ class C void M() { int i = 0; - N({|IDE0310:{{anonymousFunctionSyntax}}|}); + N({|IDE0320:{{anonymousFunctionSyntax}}|}); } void N(Func f) @@ -329,9 +329,9 @@ class C { void M() { - N({|IDE0310:() => + N({|IDE0320:() => { - Action a = {|IDE0310:() => { }|}; + Action a = {|IDE0320:() => { }|}; }|}); } @@ -373,9 +373,9 @@ class C { void M() { - N({|IDE0310:delegate () + N({|IDE0320:delegate () { - Action a = {|IDE0310:delegate () { }|}; + Action a = {|IDE0320:delegate () { }|}; }|}); } diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 1373929bacae6..99b146a48c43e 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -96,7 +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 = /*IDE0310*/ 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/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index 6c4aec2037b4e..7d4b190395472 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -199,7 +199,7 @@ internal static class IDEDiagnosticIds public const string UseCollectionExpressionForBuilderDiagnosticId = "IDE0304"; public const string UseCollectionExpressionForFluentDiagnosticId = "IDE0305"; - public const string MakeAnonymousFunctionStaticDiagnosticId = "IDE0310"; + public const string MakeAnonymousFunctionStaticDiagnosticId = "IDE0320"; // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 164f6ad6bb559..6b82044ca95c0 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -479,8 +479,8 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE0305 dotnet_diagnostic.IDE0305.severity = %value% - # IDE0310 - dotnet_diagnostic.IDE0310.severity = %value% + # IDE0320 + dotnet_diagnostic.IDE0320.severity = %value% # IDE1005 dotnet_diagnostic.IDE1005.severity = %value% @@ -895,7 +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"), - ("IDE0310", "csharp_prefer_static_anonymous_function", "true"), + ("IDE0320", "csharp_prefer_static_anonymous_function", "true"), ("IDE1005", "csharp_style_conditional_delegate_call", "true"), ("IDE1006", null, null), ("IDE1007", null, null), From feb8c29788902410afb31c404caa10dd9a9ed2c1 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 22:07:05 +0300 Subject: [PATCH 0345/1047] Lower code action priority --- .../CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs index 2c0071a9d9e8c..4691f825733e9 100644 --- a/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/MakeAnonymousFunctionStatic/CSharpMakeAnonymousFunctionStaticCodeFixProvider.cs @@ -26,7 +26,12 @@ internal sealed class CSharpMakeAnonymousFunctionStaticCodeFixProvider() : Synta public override Task RegisterCodeFixesAsync(CodeFixContext context) { - RegisterCodeFix(context, CSharpAnalyzersResources.Make_anonymous_function_static, nameof(CSharpAnalyzersResources.Make_anonymous_function_static)); + 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; } From 8668c6d65ab6d81e8519bec37dd5747ab7c61eea Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 22:08:45 +0300 Subject: [PATCH 0346/1047] Simplify example --- .../CSharp/Impl/Options/Formatting/StyleViewModel.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index 166ffa7af88cc..7b7dfdecfe67e 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -1552,15 +1552,11 @@ int fibonacci(int n) using System; //[ // {{ServicesVSResources.Prefer_colon}} - Func f1 = static i => i * i; - Func f2 = static delegate (int i) { return i * i; }; - Func f3 = static (i, j) => i * j; + Func f = static i => i * i; //] //[ // {{ServicesVSResources.Over_colon}} - Func f1 = i => i * i; - Func f2 = delegate (int i) { return i * i; }; - Func f3 = (i, j) => i * j; + Func f = i => i * i; //] """; From 41bfda6c478914069d380df0ef0a5eacea81ccc4 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 22:15:38 +0300 Subject: [PATCH 0347/1047] Add test --- .../MakeAnonymousFunctionStaticTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs index f3f98bfccfc09..d08492d3bc96c 100644 --- a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs @@ -184,6 +184,51 @@ 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({|IDE0320:{{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")] From 7c64a3515e4be3934c63756f7e60980da093374b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 22:27:41 +0300 Subject: [PATCH 0348/1047] Option work --- .../CodeStyle/CSharpCodeStyleSettingsProvider.cs | 2 +- .../Options/AutomationObject/AutomationObject.Style.cs | 6 ++++++ .../DataProvider/DataProviderTests.cs | 5 ++--- .../Core/Def/Options/VisualStudioOptionStorage.cs | 8 +------- .../CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs | 1 + 5 files changed, 11 insertions(+), 11 deletions(-) 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/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/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs b/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs index 14e4d2d695301..cc30ed940278c 100644 --- a/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs +++ b/src/VisualStudio/CSharp/Test/EditorConfigSettings/DataProvider/DataProviderTests.cs @@ -165,10 +165,9 @@ public void TestGettingCodeStyleSettingsProviderLanguageServiceAsync() settingsProvider.RegisterViewModel(model); var dataSnapShot = settingsProvider.GetCurrentDataSnapshot(); - // We don't support PreferredModifierOrder and PreferStaticAnonymousFunction yet: + // We don't support PreferredModifierOrder yet: var optionsWithUI = CSharpCodeStyleOptions.AllOptions - .Remove(CSharpCodeStyleOptions.PreferredModifierOrder) - .Remove(CSharpCodeStyleOptions.PreferStaticAnonymousFunction); + .Remove(CSharpCodeStyleOptions.PreferredModifierOrder); AssertEx.SetEqual(optionsWithUI, dataSnapShot.Select(setting => setting.Key.Option)); } diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 200238b7eacd3..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; @@ -171,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")}, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs index 3dab88bb89ec6..e257332c63b21 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpIdeCodeStyleOptions.cs @@ -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); } } From 939f76c44152570eab9aa8dcfe4dca518396a980 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 14:10:34 -0700 Subject: [PATCH 0349/1047] Change all messages to pass back their checksum. Switch to callback style --- .../Fakes/SimpleAssetSource.cs | 6 ++-- .../Core/RemoteHostAssetSerialization.cs | 27 +++++++--------- .../Remote/ServiceHub/Host/AssetProvider.cs | 32 ++++++------------- .../Remote/ServiceHub/Host/IAssetSource.cs | 2 +- .../ServiceHub/Host/SolutionAssetSource.cs | 2 +- 5 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index 66af4956e40c8..22ee69dda60ca 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs @@ -19,9 +19,8 @@ namespace Microsoft.CodeAnalysis.Remote.Testing; internal sealed class SimpleAssetSource(ISerializerService serializerService, IReadOnlyDictionary map) : IAssetSource { public ValueTask GetAssetsAsync( - Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService deserializerService, Action callback, TArg arg, CancellationToken cancellationToken) + Checksum solutionChecksum, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService deserializerService, Action callback, TArg arg, CancellationToken cancellationToken) { - var index = 0; foreach (var checksum in checksums.Span) { Contract.ThrowIfFalse(map.TryGetValue(checksum, out var data)); @@ -38,8 +37,7 @@ public ValueTask GetAssetsAsync( using var reader = ObjectReader.GetReader(stream, leaveOpen: true, cancellationToken); var asset = deserializerService.Deserialize(data.GetWellKnownSynchronizationKind(), reader, cancellationToken); Contract.ThrowIfNull(asset); - callback(index, (T)asset, arg); - index++; + callback(checksum, (T)asset, arg); } return ValueTaskFactory.CompletedTask; diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 055a8d641142b..37a7c7e1c8bc1 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -43,29 +43,23 @@ public static async ValueTask WriteDataAsync( { var checksum = checksums.Span[i]; var asset = assetMap[checksum]; + Contract.ThrowIfNull(asset); + + var kind = asset.GetWellKnownSynchronizationKind(); + checksum.WriteTo(writer); + writer.WriteInt32((int)kind); + serializer.Serialize(asset, writer, context, cancellationToken); // 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, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, 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 @@ -77,7 +71,7 @@ public static ValueTask ReadDataAsync( return ReadDataSuppressedFlowAsync(pipeReader, solutionChecksum, objectCount, serializerService, callback, arg, cancellationToken); static async ValueTask ReadDataSuppressedFlowAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { using var stream = await pipeReader.AsPrebufferedStreamAsync(cancellationToken).ConfigureAwait(false); ReadData(stream, solutionChecksum, objectCount, serializerService, callback, arg, cancellationToken); @@ -85,7 +79,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( } public static void ReadData( - Stream stream, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + Stream stream, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { using var reader = ObjectReader.GetReader(stream, leaveOpen: true, cancellationToken); @@ -96,12 +90,13 @@ public static void ReadData( for (int i = 0, n = objectCount; i < n; i++) { + var checksum = Checksum.ReadFrom(reader); var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); // in service hub, cancellation means simply closed stream var result = serializerService.Deserialize(kind, reader, cancellationToken); Contract.ThrowIfNull(result); - callback(i, (T)result, arg); + callback(checksum, (T)result, arg); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index e754650d6939a..043b0db55733a 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -284,16 +284,21 @@ private async ValueTask SynchronizeAssetsAsync( if (missingChecksumsCount > 0) { var missingChecksumsMemory = new ReadOnlyMemory(missingChecksums, 0, missingChecksumsCount); + Contract.ThrowIfTrue(missingChecksumsMemory.Length == 0); - await RequestAssetsAsync( - assetPath, missingChecksumsMemory, +#if NETCOREAPP + Contract.ThrowIfTrue(missingChecksumsMemory.Span.Contains(Checksum.Null)); +#else + Contract.ThrowIfTrue(missingChecksumsMemory.Span.IndexOf(Checksum.Null) >= 0); +#endif + + await _assetSource.GetAssetsAsync( + _solutionChecksum, assetPath, missingChecksumsMemory, _serializerService, static ( - int index, + Checksum missingChecksum, T missingAsset, (AssetProvider assetProvider, Checksum[] missingChecksums, Action? callback, TArg? arg) tuple) => { - var missingChecksum = tuple.missingChecksums[index]; - tuple.callback?.Invoke(missingChecksum, missingAsset, tuple.arg!); tuple.assetProvider._assetCache.GetOrAdd(missingChecksum, missingAsset!); }, @@ -307,22 +312,5 @@ await RequestAssetsAsync( s_checksumPool.Free(missingChecksums); } } - - return; - } - - private async ValueTask RequestAssetsAsync( - AssetPath assetPath, ReadOnlyMemory checksums, Action callback, TArg arg, CancellationToken cancellationToken) - { -#if NETCOREAPP - Contract.ThrowIfTrue(checksums.Span.Contains(Checksum.Null)); -#else - Contract.ThrowIfTrue(checksums.Span.IndexOf(Checksum.Null) >= 0); -#endif - - if (checksums.Length == 0) - return; - - await _assetSource.GetAssetsAsync(_solutionChecksum, assetPath, checksums, _serializerService, callback, arg, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs index 018cb7f73e72d..f9f08c0580ac1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/IAssetSource.cs @@ -20,7 +20,7 @@ ValueTask GetAssetsAsync( AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializerService, - Action callback, + Action callback, TArg arg, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index a46e6f905385e..c85c67923a5ca 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -21,7 +21,7 @@ public async ValueTask GetAssetsAsync( AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializerService, - Action assetCallback, + Action assetCallback, TArg arg, CancellationToken cancellationToken) { From f4fa565dd0d4bab81deb8d436cd4647f9e3f2222 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 14:12:17 -0700 Subject: [PATCH 0350/1047] Simplify loop. No need to preserve order --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 37a7c7e1c8bc1..a317e756f450a 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -39,10 +39,8 @@ public static async ValueTask WriteDataAsync( Debug.Assert(assetMap != null); - for (var i = 0; i < checksums.Span.Length; i++) + foreach (var (checksum, asset) in assetMap) { - var checksum = checksums.Span[i]; - var asset = assetMap[checksum]; Contract.ThrowIfNull(asset); var kind = asset.GetWellKnownSynchronizationKind(); From ccaa9d8d340ec0a37c8c47462abff38a67faf20d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 14:18:34 -0700 Subject: [PATCH 0351/1047] Simplify stream code --- .../Remote/Core/SolutionAssetProvider.cs | 3 +- .../Core/Utilities/SerializableBytes.cs | 29 ++++--------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index d4df3ec47e989..a2c08b712fcdb 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -73,13 +73,14 @@ private async ValueTask WriteAssetsWorkerAsync( var serializer = _services.GetRequiredService(); var scope = assetStorage.GetScope(solutionChecksum); + using var stream = new PipeWriterStream(pipeWriter); + using var _ = Creator.CreateResultMap(out var resultMap); await scope.AddAssetsAsync(assetPath, checksums, resultMap, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - using var stream = new PipeWriterStream(pipeWriter); await RemoteHostAssetSerialization.WriteDataAsync( stream, resultMap, serializer, scope.ReplicationContext, solutionChecksum, checksums, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs index 4db53c7e33f27..766d232a93dc4 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; @@ -95,7 +92,7 @@ private static void BlowChunks(byte[][]? chunks) internal static PooledStream CreateWritableStream() => new ReadWriteStream(); - 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 + private sealed class ReadWriteStream : PooledStream { public ReadWriteStream() : base(length: 0, chunks: SharedPools.BigDefault>().AllocateAndClear()) From 79cf47586f8e2a515f34fdc3e64b5b80d6dc2092 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:00:00 -0700 Subject: [PATCH 0352/1047] Finding callback --- .../Portable/Workspace/Solution/Checksum.cs | 37 ++++++++ .../Core/RemoteHostAssetSerialization.cs | 39 -------- .../Remote/Core/SolutionAssetProvider.cs | 95 +++++++++++++++++-- 3 files changed, 125 insertions(+), 46 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index c394b17331d26..adb866a17f3ad 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -3,12 +3,15 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; @@ -23,6 +26,8 @@ internal readonly partial record struct Checksum( [field: FieldOffset(0)][property: DataMember(Order = 0)] long Data1, [field: FieldOffset(8)][property: DataMember(Order = 1)] long Data2) { + private static readonly ObjectPool s_bytesPool = new(() => new byte[HashSize]); + /// /// The intended size of the structure. /// @@ -82,6 +87,21 @@ public void WriteTo(ObjectWriter writer) writer.WriteInt64(Data1); writer.WriteInt64(Data2); } +#if false + public void WriteTo(Stream stream) + { +#if NET + Span bytes = stackalloc byte[HashSize]; + this.WriteTo(bytes); + stream.Write(bytes); +#else + var bytes = s_bytesPool.Allocate(); + this.WriteTo(bytes); + stream.Write(bytes, 0, HashSize); + s_bytesPool.Free(bytes); +#endif + } +#endif public void WriteTo(Span span) { @@ -92,6 +112,23 @@ public void WriteTo(Span span) public static Checksum ReadFrom(ObjectReader reader) => new(reader.ReadInt64(), reader.ReadInt64()); +#if false + public static Checksum ReadFrom(Stream stream) + { +#if NET + Span bytes = stackalloc byte[HashSize]; + stream.Read(bytes); + return From(bytes); +#else + var bytes = s_bytesPool.Allocate(); + stream.Read(bytes, 0, HashSize); + var checksum = From(bytes); + s_bytesPool.Free(bytes); + return checksum; +#endif + } +#endif + public static Func GetChecksumLogInfo { get; } = checksum => checksum.ToString(); diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index a317e756f450a..0fa89303010ce 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -17,45 +17,6 @@ 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); - - foreach (var (checksum, asset) in assetMap) - { - Contract.ThrowIfNull(asset); - - var kind = asset.GetWellKnownSynchronizationKind(); - checksum.WriteTo(writer); - writer.WriteInt32((int)kind); - serializer.Serialize(asset, writer, context, cancellationToken); - - // 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 stream.FlushAsync(cancellationToken).ConfigureAwait(false); - } - } - public static ValueTask ReadDataAsync( PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index a2c08b712fcdb..7db6733739c01 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -4,11 +4,15 @@ using System; using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; @@ -19,6 +23,8 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class SolutionAssetProvider(SolutionServices services) : ISolutionAssetProvider { + private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + public const string ServiceName = "SolutionAssetProvider"; internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); @@ -73,19 +79,94 @@ private async ValueTask WriteAssetsWorkerAsync( var serializer = _services.GetRequiredService(); var scope = assetStorage.GetScope(solutionChecksum); - using var stream = new PipeWriterStream(pipeWriter); + var pipeWriterStream = pipeWriter.AsStream(); + + var foundChecksumCount = 0; + + await scope.AddAssetsAsync( + assetPath, + checksums, + async (Checksum checksum, object asset) => + { + Contract.ThrowIfNull(asset); + Interlocked.Increment(ref foundChecksumCount); + + using var pooledObject = s_streamPool.GetPooledObject(); + var tempStream = pooledObject.Object; + tempStream.Position = 0; + tempStream.SetLength(0); + + // Write the asset to a temporary buffer so we can calculate its length. + using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); + { + // 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. + var kind = asset.GetWellKnownSynchronizationKind(); + objectWriter.WriteInt32((int)kind); - using var _ = Creator.CreateResultMap(out var resultMap); + // Now serialize out the asset itself. + serializer.Serialize(asset, objectWriter, scope.ReplicationContext, cancellationToken); + } - await scope.AddAssetsAsync(assetPath, checksums, resultMap, cancellationToken).ConfigureAwait(false); + // Write the length of the asset to the pipe writer. + Contract.ThrowIfTrue(tempStream.Length > int.MaxValue); + WriteInt32(pipeWriterStream, (int)tempStream.Length); - cancellationToken.ThrowIfCancellationRequested(); + // Ensure we flush out the length so the reading side knows how much data to read. + await pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); - await RemoteHostAssetSerialization.WriteDataAsync( - stream, resultMap, serializer, scope.ReplicationContext, - solutionChecksum, checksums, cancellationToken).ConfigureAwait(false); + // Now, asynchronously copy the temp buffer over to the writer stream. + tempStream.Position = 0; + await tempStream.CopyToAsync(pipeWriterStream, 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 pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); + + //cancellationToken.ThrowIfCancellationRequested(); + + //await RemoteHostAssetSerialization.WriteDataAsync( + // stream, resultMap, serializer, scope.ReplicationContext, + // solutionChecksum, checksums, cancellationToken).ConfigureAwait(false); } +#if false + 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); + + // special case + if (checksums.Length == 0) + return; + + Debug.Assert(assetMap != null); + + foreach (var (checksum, asset) in assetMap) + { + Contract.ThrowIfNull(asset); + + var kind = asset.GetWellKnownSynchronizationKind(); + checksum.WriteTo(writer); + writer.WriteInt32((int)kind); + + } + } +#endif + /// /// Simple port of /// https://github.com/AArnott/Nerdbank.Streams/blob/dafeb5846702bc29e261c9ddf60f42feae01654c/src/Nerdbank.Streams/BufferWriterStream.cs#L16. From 9f63ea7640cbc75df26c9eb60ecdbfd471eaf366 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:27:22 -0700 Subject: [PATCH 0353/1047] Fleshout --- .../Workspace/Solution/ChecksumCollection.cs | 14 ++-- .../Workspace/Solution/StateChecksums.cs | 81 ++++++++++++------- .../Remote/Core/SolutionAssetProvider.cs | 75 ++++++----------- .../Remote/Core/SolutionAssetStorage.Scope.cs | 30 ++++--- 4 files changed, 105 insertions(+), 95 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index e2a2d6fef2a46..001555ef464dd 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -58,7 +58,7 @@ internal static async Task FindAsync( AssetPath assetPath, TextDocumentStates documentStates, HashSet searchingChecksumsLeft, - Dictionary result, + Func onAssetFoundAsync, CancellationToken cancellationToken) where TState : TextDocumentState { var hintDocument = assetPath.DocumentId; @@ -68,7 +68,8 @@ internal static async Task FindAsync( if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync( + assetPath, state, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } else @@ -81,16 +82,17 @@ internal static async Task FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync( + assetPath, state, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } } - internal static void Find( + internal static async Task FindAsync( IReadOnlyList values, ChecksumCollection checksums, HashSet searchingChecksumsLeft, - Dictionary result, + Func onAssetFoundAsync, CancellationToken cancellationToken) where T : class { Contract.ThrowIfFalse(values.Count == checksums.Children.Length); @@ -103,7 +105,7 @@ internal static void Find( var checksum = checksums.Children[i]; if (searchingChecksumsLeft.Remove(checksum)) - result[checksum] = values[i]; + await onAssetFoundAsync(checksum, values[i], cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 7762b7c92f61f..30dc783288876 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -118,7 +118,7 @@ public async Task FindAsync( ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Func onAssetFoundAsync, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -128,10 +128,10 @@ public async Task FindAsync( if (assetPath.IncludeSolutionCompilationState) { if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) - result[this.Checksum] = this; + await onAssetFoundAsync(this.Checksum, this, cancellationToken).ConfigureAwait(false); if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) - result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; + await onAssetFoundAsync(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); if (compilationState.FrozenSourceGeneratedDocumentStates != null) { @@ -143,7 +143,7 @@ public async Task FindAsync( { await ChecksumCollection.FindAsync( new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), - compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFoundAsync, 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 @@ -161,7 +161,7 @@ await ChecksumCollection.FindAsync( if (searchingChecksumsLeft.Remove(identityChecksum)) { Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); - result[identityChecksum] = state.Identity; + await onAssetFoundAsync(identityChecksum, state.Identity, cancellationToken).ConfigureAwait(false); } } } @@ -175,7 +175,7 @@ await ChecksumCollection.FindAsync( { var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - result[identityChecksum] = state.Identity; + await onAssetFoundAsync(identityChecksum, state.Identity, cancellationToken).ConfigureAwait(false); } } } @@ -189,13 +189,15 @@ await ChecksumCollection.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, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync( + solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFoundAsync, 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, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync( + solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } } @@ -269,7 +271,7 @@ public async Task FindAsync( ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Func onAssetFoundAsync, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -279,13 +281,16 @@ public async Task FindAsync( if (assetPath.IncludeSolutionState) { if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; + await onAssetFoundAsync(Checksum, this, cancellationToken).ConfigureAwait(false); if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) - result[Attributes] = solution.SolutionAttributes; + await onAssetFoundAsync(Attributes, solution.SolutionAttributes, cancellationToken).ConfigureAwait(false); if (assetPath.IncludeSolutionAnalyzerReferences) - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + { + await ChecksumCollection.FindAsync( + solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + } } if (searchingChecksumsLeft.Count == 0) @@ -304,7 +309,8 @@ public async Task FindAsync( if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) { - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync( + projectState, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } else @@ -327,7 +333,8 @@ public async Task FindAsync( if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) continue; - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync( + projectState, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } } @@ -435,7 +442,7 @@ public async Task FindAsync( ProjectState state, AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Func onAssetFoundAsync, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -449,32 +456,47 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; + await onAssetFoundAsync(Checksum, this, cancellationToken).ConfigureAwait(false); if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) - result[Info] = state.ProjectInfo.Attributes; + await onAssetFoundAsync(Info, state.ProjectInfo.Attributes, cancellationToken).ConfigureAwait(false); if (assetPath.IncludeProjectCompilationOptions && searchingChecksumsLeft.Remove(CompilationOptions)) - result[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."); + { + 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."); + await onAssetFoundAsync(CompilationOptions, compilationOptions, cancellationToken).ConfigureAwait(false); + } if (assetPath.IncludeProjectParseOptions && searchingChecksumsLeft.Remove(ParseOptions)) - result[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."); + { + 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."); ; + await onAssetFoundAsync(ParseOptions, parseOptions, cancellationToken).ConfigureAwait(false); + } if (assetPath.IncludeProjectProjectReferences) - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); + { + await ChecksumCollection.FindAsync( + state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + } if (assetPath.IncludeProjectMetadataReferences) - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); + { + await ChecksumCollection.FindAsync( + state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + } if (assetPath.IncludeProjectAnalyzerReferences) - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + { + await ChecksumCollection.FindAsync( + state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + } } if (assetPath.IncludeDocuments) { - await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } } @@ -500,7 +522,7 @@ public async Task FindAsync( AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, - Dictionary result, + Func onAssetFoundAsync, CancellationToken cancellationToken) { Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum); @@ -508,10 +530,13 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) - result[Info] = state.Attributes; + await onAssetFoundAsync(Info, state.Attributes, cancellationToken).ConfigureAwait(false); if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) - result[Text] = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); + { + var text = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); + await onAssetFoundAsync(Text, text, cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 7db6733739c01..c0c03e8915df5 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -5,8 +5,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.IO.Pipelines; using System.Threading; @@ -86,40 +84,27 @@ private async ValueTask WriteAssetsWorkerAsync( await scope.AddAssetsAsync( assetPath, checksums, - async (Checksum checksum, object asset) => + async (checksum, asset, cancellationToken) => { Contract.ThrowIfNull(asset); - Interlocked.Increment(ref foundChecksumCount); + foundChecksumCount++; using var pooledObject = s_streamPool.GetPooledObject(); var tempStream = pooledObject.Object; tempStream.Position = 0; tempStream.SetLength(0); - // Write the asset to a temporary buffer so we can calculate its length. - using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); - { - // Write the checksum for the asset we're writing out, so the other side knows what asset this is. - checksum.WriteTo(objectWriter); + WriteAssetToTempStream(tempStream, checksum, asset); - // Write out the kind so the receiving end knows how to deserialize this asset. - var kind = asset.GetWellKnownSynchronizationKind(); - objectWriter.WriteInt32((int)kind); - - // Now serialize out the asset itself. - serializer.Serialize(asset, objectWriter, scope.ReplicationContext, cancellationToken); - } - - // Write the length of the asset to the pipe writer. - Contract.ThrowIfTrue(tempStream.Length > int.MaxValue); - WriteInt32(pipeWriterStream, (int)tempStream.Length); + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteLengthToPipeWriter(tempStream.Length); // Ensure we flush out the length so the reading side knows how much data to read. await pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); // Now, asynchronously copy the temp buffer over to the writer stream. tempStream.Position = 0; - await tempStream.CopyToAsync(pipeWriterStream, cancellationToken).ConfigureAwait(false); + 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 @@ -130,42 +115,34 @@ await scope.AddAssetsAsync( Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); - //cancellationToken.ThrowIfCancellationRequested(); - - //await RemoteHostAssetSerialization.WriteDataAsync( - // stream, resultMap, serializer, scope.ReplicationContext, - // solutionChecksum, checksums, cancellationToken).ConfigureAwait(false); - } + return; -#if false - 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); + void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) + { + // Write the asset to a temporary buffer so we can calculate its length. + using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); + { + // Write the checksum for the asset we're writing out, so the other side knows what asset this is. + checksum.WriteTo(objectWriter); - // special case - if (checksums.Length == 0) - return; + // Write out the kind so the receiving end knows how to deserialize this asset. + var kind = asset.GetWellKnownSynchronizationKind(); + objectWriter.WriteInt32((int)kind); - Debug.Assert(assetMap != null); + // Now serialize out the asset itself. + serializer.Serialize(asset, objectWriter, scope.ReplicationContext, cancellationToken); + } + } - foreach (var (checksum, asset) in assetMap) + void WriteLengthToPipeWriter(long length) { - Contract.ThrowIfNull(asset); - - var kind = asset.GetWellKnownSynchronizationKind(); - checksum.WriteTo(writer); - writer.WriteInt32((int)kind); + Contract.ThrowIfTrue(length > int.MaxValue); + var span = pipeWriter.GetSpan(sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); + pipeWriter.Advance(span.Length); } } -#endif /// /// Simple port of diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index e4b6ae9f169a8..532fd5d543c35 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -47,7 +47,7 @@ public void Dispose() public async Task AddAssetsAsync( AssetPath assetPath, ReadOnlyMemory checksums, - Dictionary assetMap, + Func onAssetFoundAsync, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -58,14 +58,16 @@ public async Task AddAssetsAsync( var numberOfChecksumsToSearch = checksumsToFind.Count; Contract.ThrowIfTrue(checksumsToFind.Contains(Checksum.Null)); - await FindAssetsAsync(assetPath, checksumsToFind, assetMap, cancellationToken).ConfigureAwait(false); + await FindAssetsAsync(assetPath, checksumsToFind, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); - Contract.ThrowIfTrue(assetMap.Count != numberOfChecksumsToSearch); } private async Task FindAssetsAsync( - AssetPath assetPath, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) + AssetPath assetPath, + HashSet remainingChecksumsToFind, + Func onAssetFoundAsync, + CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -74,13 +76,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, assetPath, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFoundAsync, 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, assetPath, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); } } @@ -98,15 +100,19 @@ 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); - await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksumPool.Object, resultPool, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(resultPool.Count != 1); + object? asset = null; + await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksumPool.Object, (foundChecksum, foundAsset, _) => + { + Contract.ThrowIfTrue(asset != null); // We should only find one asset + Contract.ThrowIfTrue(checksum != foundChecksum); + asset = foundAsset; + return ValueTaskFactory.CompletedTask; + }, cancellationToken).ConfigureAwait(false); - var (resultingChecksum, value) = resultPool.First(); - Contract.ThrowIfFalse(checksum == resultingChecksum); + Contract.ThrowIfNull(asset); - return value; + return asset; } } } From 0e78acc7d9192266b81d2283e4934fbc9a76cd29 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:28:44 -0700 Subject: [PATCH 0354/1047] Extract --- .../Remote/Core/SolutionAssetProvider.cs | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index c0c03e8915df5..8d7cdc3183f28 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -84,38 +84,41 @@ private async ValueTask WriteAssetsWorkerAsync( await scope.AddAssetsAsync( assetPath, checksums, - async (checksum, asset, cancellationToken) => - { - Contract.ThrowIfNull(asset); - foundChecksumCount++; + WriteAssetToPipeAsync, + cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); - using var pooledObject = s_streamPool.GetPooledObject(); - var tempStream = pooledObject.Object; - tempStream.Position = 0; - tempStream.SetLength(0); + return; - WriteAssetToTempStream(tempStream, checksum, asset); + async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(asset); + foundChecksumCount++; - // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLengthToPipeWriter(tempStream.Length); + using var pooledObject = s_streamPool.GetPooledObject(); + var tempStream = pooledObject.Object; + tempStream.Position = 0; + tempStream.SetLength(0); - // Ensure we flush out the length so the reading side knows how much data to read. - await pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); + WriteAssetToTempStream(tempStream, checksum, asset); - // Now, asynchronously copy the temp buffer over to the writer stream. - tempStream.Position = 0; - await tempStream.CopyToAsync(pipeWriter, cancellationToken).ConfigureAwait(false); + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteLengthToPipeWriter(tempStream.Length); - // 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 pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); - }, cancellationToken).ConfigureAwait(false); + // Ensure we flush out the length so the reading side knows how much data to read. + await pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); + // Now, asynchronously copy the temp buffer over to the writer stream. + tempStream.Position = 0; + await tempStream.CopyToAsync(pipeWriter, cancellationToken).ConfigureAwait(false); - return; + // 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 pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); + } void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) { From df93e6cce0d82ebdae06220b9a1d9953e6f3f481 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:30:12 -0700 Subject: [PATCH 0355/1047] Remove type --- src/Workspaces/Remote/Core/SolutionAssetProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 8d7cdc3183f28..06fd8043250ad 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -160,6 +160,7 @@ void WriteLengthToPipeWriter(long length) /// is holding onto (including within , , or ). /// Responsibility for that is solely in the hands of . /// +#if false private class PipeWriterStream : Stream, IDisposableObservable { private readonly PipeWriter _writer; @@ -290,5 +291,6 @@ public override void SetLength(long value) #endregion } +#endif } } From 2e7ed2a917fdad401b2014c54a94d3c3249b882a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:32:44 -0700 Subject: [PATCH 0356/1047] Docs --- src/Workspaces/Remote/Core/SolutionAssetProvider.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 06fd8043250ad..74ffeba8d3dc8 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -101,6 +101,9 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat tempStream.Position = 0; tempStream.SetLength(0); + // 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 fasion. WriteAssetToTempStream(tempStream, checksum, asset); // Write the length of the asset to the pipe writer so the reader knows how much data to read. @@ -122,7 +125,6 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) { - // Write the asset to a temporary buffer so we can calculate its length. using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); { // Write the checksum for the asset we're writing out, so the other side knows what asset this is. @@ -147,6 +149,7 @@ void WriteLengthToPipeWriter(long length) } } +#if false /// /// Simple port of /// https://github.com/AArnott/Nerdbank.Streams/blob/dafeb5846702bc29e261c9ddf60f42feae01654c/src/Nerdbank.Streams/BufferWriterStream.cs#L16. @@ -160,7 +163,6 @@ void WriteLengthToPipeWriter(long length) /// is holding onto (including within , , or ). /// Responsibility for that is solely in the hands of . /// -#if false private class PipeWriterStream : Stream, IDisposableObservable { private readonly PipeWriter _writer; From 39fef3da8ba6834700e62c8319372b0cbde63086 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:36:26 -0700 Subject: [PATCH 0357/1047] move back --- .../Core/RemoteHostAssetSerialization.cs | 84 +++++++++++++++++++ .../Remote/Core/SolutionAssetProvider.cs | 77 +---------------- .../Remote/Core/SolutionAssetStorage.Scope.cs | 1 - 3 files changed, 88 insertions(+), 74 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 0fa89303010ce..a316225f36320 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -3,12 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Collections.Generic; 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; @@ -17,6 +19,88 @@ namespace Microsoft.CodeAnalysis.Remote { internal static class RemoteHostAssetSerialization { + private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + + public static async ValueTask WriteDataAsync( + PipeWriter pipeWriter, + AssetPath assetPath, + ReadOnlyMemory checksums, + SolutionAssetStorage.Scope scope, + ISerializerService serializer, + CancellationToken cancellationToken) + { + var pipeWriterStream = pipeWriter.AsStream(); + + var foundChecksumCount = 0; + + await scope.AddAssetsAsync( + assetPath, + checksums, + WriteAssetToPipeAsync, + cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); + + return; + + async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(asset); + foundChecksumCount++; + + using var pooledObject = s_streamPool.GetPooledObject(); + var tempStream = pooledObject.Object; + tempStream.Position = 0; + tempStream.SetLength(0); + + // 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 fasion. + WriteAssetToTempStream(tempStream, checksum, asset); + + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteLengthToPipeWriter(tempStream.Length); + + // Ensure we flush out the length so the reading side knows how much data to read. + await pipeWriterStream.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 pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) + { + using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); + { + // 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. + var kind = asset.GetWellKnownSynchronizationKind(); + objectWriter.WriteInt32((int)kind); + + // Now serialize out the asset itself. + serializer.Serialize(asset, objectWriter, scope.ReplicationContext, cancellationToken); + } + } + + void WriteLengthToPipeWriter(long length) + { + Contract.ThrowIfTrue(length > int.MaxValue); + + var span = pipeWriter.GetSpan(sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); + pipeWriter.Advance(span.Length); + } + } + public static ValueTask ReadDataAsync( PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 74ffeba8d3dc8..17c290b64c1a3 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -21,8 +21,6 @@ namespace Microsoft.CodeAnalysis.Remote /// internal sealed class SolutionAssetProvider(SolutionServices services) : ISolutionAssetProvider { - private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); - public const string ServiceName = "SolutionAssetProvider"; internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); @@ -66,7 +64,7 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s } } - private async ValueTask WriteAssetsWorkerAsync( + private ValueTask WriteAssetsWorkerAsync( PipeWriter pipeWriter, Checksum solutionChecksum, AssetPath assetPath, @@ -74,79 +72,12 @@ private async ValueTask WriteAssetsWorkerAsync( CancellationToken cancellationToken) { var assetStorage = _services.GetRequiredService().AssetStorage; - var serializer = _services.GetRequiredService(); var scope = assetStorage.GetScope(solutionChecksum); - var pipeWriterStream = pipeWriter.AsStream(); - - var foundChecksumCount = 0; - - await scope.AddAssetsAsync( - assetPath, - checksums, - WriteAssetToPipeAsync, - cancellationToken).ConfigureAwait(false); - - Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); - - return; - - async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(asset); - foundChecksumCount++; - - using var pooledObject = s_streamPool.GetPooledObject(); - var tempStream = pooledObject.Object; - tempStream.Position = 0; - tempStream.SetLength(0); - - // 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 fasion. - WriteAssetToTempStream(tempStream, checksum, asset); - - // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLengthToPipeWriter(tempStream.Length); - - // Ensure we flush out the length so the reading side knows how much data to read. - await pipeWriterStream.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 pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); - } - - void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) - { - using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); - { - // 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. - var kind = asset.GetWellKnownSynchronizationKind(); - objectWriter.WriteInt32((int)kind); - - // Now serialize out the asset itself. - serializer.Serialize(asset, objectWriter, scope.ReplicationContext, cancellationToken); - } - } - - void WriteLengthToPipeWriter(long length) - { - Contract.ThrowIfTrue(length > int.MaxValue); + var serializer = _services.GetRequiredService(); - var span = pipeWriter.GetSpan(sizeof(int)); - BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); - pipeWriter.Advance(span.Length); - } + return RemoteHostAssetSerialization.WriteDataAsync( + pipeWriter, assetPath, checksums, scope, serializer, cancellationToken); } #if false diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 532fd5d543c35..8534d04622777 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; From 5c8c8d39153a40205d38abab32388296cdf8dea3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 15:47:18 -0700 Subject: [PATCH 0358/1047] don't truncate --- .../Core/RemoteHostAssetSerialization.cs | 16 ++++++++-- .../Core/Utilities/SerializableBytes.cs | 29 ++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index a316225f36320..9b4b258381105 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Remote { internal static class RemoteHostAssetSerialization { - private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, @@ -51,7 +51,10 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat using var pooledObject = s_streamPool.GetPooledObject(); var tempStream = pooledObject.Object; tempStream.Position = 0; - tempStream.SetLength(0); + + // 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. + tempStream.SetLength(0, truncate: false); // 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. @@ -116,7 +119,14 @@ public static ValueTask ReadDataAsync( static async ValueTask ReadDataSuppressedFlowAsync( PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { - using var stream = await pipeReader.AsPrebufferedStreamAsync(cancellationToken).ConfigureAwait(false); + for (var i = 0; i < objectCount; i++) + { + // First, read the length of the asset (and header) we'll be reading. + var readResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); + + var sequenceReader = new + } + ReadData(stream, solutionChecksum, objectCount, serializerService, callback, arg, cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs index 766d232a93dc4..393a109c6815a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs @@ -89,7 +89,7 @@ private static void BlowChunks(byte[][]? chunks) } } - internal static PooledStream CreateWritableStream() + internal static ReadWriteStream CreateWritableStream() => new ReadWriteStream(); public abstract class PooledStream : Stream @@ -276,7 +276,7 @@ public override void Write(byte[] buffer, int offset, int count) { } - private sealed class ReadWriteStream : PooledStream + public sealed class ReadWriteStream : PooledStream { public ReadWriteStream() : base(length: 0, chunks: SharedPools.BigDefault>().AllocateAndClear()) @@ -318,25 +318,32 @@ private void EnsureCapacity(long value) } public override void SetLength(long value) + => SetLength(value, truncate: true); + + public void SetLength(long value, bool truncate) { EnsureCapacity(value); if (value < length) { - // truncate the stream + if (truncate) + { + var chunkIndex = GetChunkIndex(value); + var chunkOffset = GetChunkOffset(value); - var chunkIndex = GetChunkIndex(value); - var chunkOffset = GetChunkOffset(value); + Array.Clear(chunks[chunkIndex], chunkOffset, chunks[chunkIndex].Length - chunkOffset); - Array.Clear(chunks[chunkIndex], chunkOffset, chunks[chunkIndex].Length - chunkOffset); + var trimIndex = chunkIndex + 1; + for (var i = trimIndex; i < chunks.Count; i++) + SharedPools.ByteArray.Free(chunks[i]); - var trimIndex = chunkIndex + 1; - for (var i = trimIndex; i < chunks.Count; i++) + chunks.RemoveRange(trimIndex, chunks.Count - trimIndex); + } + else { - SharedPools.ByteArray.Free(chunks[i]); + // TODO: do we want to clear out the remaining arrays? Or is it fine to keep the existing data in + // it just as garbage? } - - chunks.RemoveRange(trimIndex, chunks.Count - trimIndex); } length = value; From 73d989f84005a151439f7cdb4c2b19dca7c40017 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:15:08 -0700 Subject: [PATCH 0359/1047] Fniish up --- .../Core/RemoteHostAssetSerialization.cs | 59 ++++++++++--------- .../ServiceHub/Host/SolutionAssetSource.cs | 2 +- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 9b4b258381105..2e8733932d302 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -3,16 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; -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 @@ -105,7 +103,7 @@ void WriteLengthToPipeWriter(long length) } public static ValueTask ReadDataAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, 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 @@ -114,42 +112,45 @@ public static ValueTask ReadDataAsync( // ⚠ 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, callback, arg, cancellationToken); + return ReadDataSuppressedFlowAsync(pipeReader, objectCount, serializerService, callback, arg, cancellationToken); static async ValueTask ReadDataSuppressedFlowAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { + using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); + for (var i = 0; i < objectCount; i++) { // First, read the length of the asset (and header) we'll be reading. var readResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); - - var sequenceReader = new + var length = ReadLength(readResult); + + // Advance past the length. + pipeReader.AdvanceTo(readResult.Buffer.GetPosition(sizeof(int))); + + // 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. + await pipeReader.ReadAtLeastAsync(length, cancellationToken).ConfigureAwait(false); + + using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, cancellationToken); + { + var checksum = Checksum.ReadFrom(reader); + var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); + + // in service hub, cancellation means simply closed stream + var result = serializerService.Deserialize(kind, reader, cancellationToken); + Contract.ThrowIfNull(result); + callback(checksum, (T)result, arg); + } } - - ReadData(stream, solutionChecksum, objectCount, serializerService, callback, arg, cancellationToken); } - } - public static void ReadData( - Stream stream, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) - { - 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++) + static int ReadLength(ReadResult readResult) { - var checksum = Checksum.ReadFrom(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); - - // in service hub, cancellation means simply closed stream - var result = serializerService.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(result); - callback(checksum, (T)result, arg); + var sequenceReader = new SequenceReader(readResult.Buffer); + Contract.ThrowIfFalse(sequenceReader.TryReadLittleEndian(out int length)); + return length; } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index c85c67923a5ca..eeb40a6e31fac 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -33,7 +33,7 @@ await RemoteCallback.InvokeServiceAsync( SolutionAssetProvider.ServiceDescriptor, (callback, cancellationToken) => callback.InvokeAsync( (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken), - (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg, cancellationToken), + (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, checksums.Length, serializerService, assetCallback, arg, cancellationToken), cancellationToken), cancellationToken).ConfigureAwait(false); } From 80c99020f71002a6e88b85824bf984475bbed0f9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:16:47 -0700 Subject: [PATCH 0360/1047] Remove --- .../Portable/Workspace/Solution/Checksum.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index adb866a17f3ad..d376ab32c2982 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -87,21 +87,6 @@ public void WriteTo(ObjectWriter writer) writer.WriteInt64(Data1); writer.WriteInt64(Data2); } -#if false - public void WriteTo(Stream stream) - { -#if NET - Span bytes = stackalloc byte[HashSize]; - this.WriteTo(bytes); - stream.Write(bytes); -#else - var bytes = s_bytesPool.Allocate(); - this.WriteTo(bytes); - stream.Write(bytes, 0, HashSize); - s_bytesPool.Free(bytes); -#endif - } -#endif public void WriteTo(Span span) { @@ -112,23 +97,6 @@ public void WriteTo(Span span) public static Checksum ReadFrom(ObjectReader reader) => new(reader.ReadInt64(), reader.ReadInt64()); -#if false - public static Checksum ReadFrom(Stream stream) - { -#if NET - Span bytes = stackalloc byte[HashSize]; - stream.Read(bytes); - return From(bytes); -#else - var bytes = s_bytesPool.Allocate(); - stream.Read(bytes, 0, HashSize); - var checksum = From(bytes); - s_bytesPool.Free(bytes); - return checksum; -#endif - } -#endif - public static Func GetChecksumLogInfo { get; } = checksum => checksum.ToString(); From d8f4b4dbeb2136ead5458c127ef88d6d49ee91c2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:17:08 -0700 Subject: [PATCH 0361/1047] Remove --- src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index d376ab32c2982..71fd24bad8b57 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -3,16 +3,12 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -26,8 +22,6 @@ internal readonly partial record struct Checksum( [field: FieldOffset(0)][property: DataMember(Order = 0)] long Data1, [field: FieldOffset(8)][property: DataMember(Order = 1)] long Data2) { - private static readonly ObjectPool s_bytesPool = new(() => new byte[HashSize]); - /// /// The intended size of the structure. /// From a2c5d652829a27f7f16b35c745e2bcb3b3275014 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:19:47 -0700 Subject: [PATCH 0362/1047] Remove --- .../Remote/Core/SolutionAssetProvider.cs | 150 ------------------ 1 file changed, 150 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 17c290b64c1a3..9db350117ae51 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -3,14 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; -using System.Buffers.Binary; -using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; @@ -79,151 +75,5 @@ private ValueTask WriteAssetsWorkerAsync( return RemoteHostAssetSerialization.WriteDataAsync( pipeWriter, assetPath, checksums, scope, serializer, cancellationToken); } - -#if false - /// - /// 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 - { - 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) - { - Verify.NotDisposed(this); - var span = _writer.GetSpan(1); - span[0] = value; - _writer.Advance(1); - } - -#if NET - - public override void Write(ReadOnlySpan buffer) - { - Verify.NotDisposed(this); - _writer.Write(buffer); - } - - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - this.Write(buffer.Span); - return default; - } - -#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 - } -#endif } } From 06f5bfce932b6dcf9b9f13e9d5ab551a1c4dcf80 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:24:49 -0700 Subject: [PATCH 0363/1047] Docs --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 2e8733932d302..38c16298aaf60 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Nerdbank.Streams; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote @@ -121,7 +122,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( for (var i = 0; i < objectCount; i++) { - // First, read the length of the asset (and header) we'll be reading. + // First, read the length of the data chunk we'll be reading. var readResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); var length = ReadLength(readResult); From f8b6275137107602b312091b06097309ac3975bf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:37:10 -0700 Subject: [PATCH 0364/1047] Fixup tests --- .../Remote/ServiceHub/Host/TestUtils.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 90a03a7481fc5..d05eca01bc506 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -184,20 +184,26 @@ public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { + var callback = (Checksum checksum, object asset, CancellationToken cancellationToken) => + { + map.Add(checksum, asset); + return ValueTaskFactory.CompletedTask; + }; + if (projectId == null) { var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(documentChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), callback, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -205,11 +211,11 @@ public static async Task AppendAssetMapAsync( else { var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), callback, 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, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), callback, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -225,13 +231,19 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary + { + map.Add(checksum, asset); + return ValueTaskFactory.CompletedTask; + }; + var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), map, cancellationToken).ConfigureAwait(false); + await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), callback, 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(AssetPathKind.Documents, document.State, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await documentChecksums.FindAsync(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, cancellationToken).ConfigureAwait(false); } } From 200a77c77e19bf62c2ca2f524ffbc7903c166d3a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 16:42:30 -0700 Subject: [PATCH 0365/1047] Fix tests --- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index d05eca01bc506..5c48f36502841 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -186,7 +186,7 @@ public static async Task AppendAssetMapAsync( { var callback = (Checksum checksum, object asset, CancellationToken cancellationToken) => { - map.Add(checksum, asset); + map[checksum] = asset; return ValueTaskFactory.CompletedTask; }; @@ -233,7 +233,7 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary { - map.Add(checksum, asset); + map[checksum] = asset; return ValueTaskFactory.CompletedTask; }; From 2490e4dacbfc63430f0decce155035b1f8d88a95 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 17:00:30 -0700 Subject: [PATCH 0366/1047] Ensure we advance --- .../Core/RemoteHostAssetSerialization.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 38c16298aaf60..8e70cf5f0aaf0 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -123,27 +123,34 @@ static async ValueTask ReadDataSuppressedFlowAsync( for (var i = 0; i < objectCount; i++) { // First, read the length of the data chunk we'll be reading. - var readResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); - var length = ReadLength(readResult); + var lengthReadResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); + var length = ReadLength(lengthReadResult); // Advance past the length. - pipeReader.AdvanceTo(readResult.Buffer.GetPosition(sizeof(int))); + pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(sizeof(int))); // 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. - await pipeReader.ReadAtLeastAsync(length, cancellationToken).ConfigureAwait(false); + 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 + // from within ObjectReader.GetReader below. + pipeReader.AdvanceTo(fillReadResult.Buffer.Start); + + // 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. using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, cancellationToken); - { - var checksum = Checksum.ReadFrom(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); - - // in service hub, cancellation means simply closed stream - var result = serializerService.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(result); - callback(checksum, (T)result, arg); - } + + var checksum = Checksum.ReadFrom(reader); + var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); + + // in service hub, cancellation means simply closed stream + var result = serializerService.Deserialize(kind, reader, cancellationToken); + Contract.ThrowIfNull(result); + callback(checksum, (T)result, arg); } } From b29adb509de6ac08447c339bb332335217430c39 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:06:32 -0700 Subject: [PATCH 0367/1047] write length properly --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 8e70cf5f0aaf0..7694647199ec5 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -99,7 +99,7 @@ void WriteLengthToPipeWriter(long length) var span = pipeWriter.GetSpan(sizeof(int)); BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); - pipeWriter.Advance(span.Length); + pipeWriter.Advance(sizeof(int)); } } @@ -124,9 +124,9 @@ static async ValueTask ReadDataSuppressedFlowAsync( { // First, read the length of the data chunk we'll be reading. var lengthReadResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); - var length = ReadLength(lengthReadResult); - // Advance past the length. + // read and advance past the length. + var length = ReadLength(lengthReadResult); pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(sizeof(int))); // Now buffer in the rest of the data we need to read. Because we're reading as much data in as From 5b678548c461b74b891d93e58ae27cdec80941da Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:12:07 -0700 Subject: [PATCH 0368/1047] proper ordering --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 043b0db55733a..614dd205e7af2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -299,8 +299,8 @@ await _assetSource.GetAssetsAsync( T missingAsset, (AssetProvider assetProvider, Checksum[] missingChecksums, Action? callback, TArg? arg) tuple) => { + missingAsset = (T)tuple.assetProvider._assetCache.GetOrAdd(missingChecksum, missingAsset!); tuple.callback?.Invoke(missingChecksum, missingAsset, tuple.arg!); - tuple.assetProvider._assetCache.GetOrAdd(missingChecksum, missingAsset!); }, (this, missingChecksums, callback, arg), cancellationToken).ConfigureAwait(false); From de09c640118f004198266665515c8caab1946cdc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:12:32 -0700 Subject: [PATCH 0369/1047] Docs --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index 614dd205e7af2..56c0b37c6e900 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -299,7 +299,10 @@ await _assetSource.GetAssetsAsync( 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), From 6de03345a167709b0f70c53aa7acf3a654b0a488 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:25:17 -0700 Subject: [PATCH 0370/1047] comment --- .../Core/RemoteHostAssetSerialization.cs | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 7694647199ec5..bf3d157ce4347 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -16,8 +16,42 @@ namespace Microsoft.CodeAnalysis.Remote { + /// + /// 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: + /// + /// ------------------------------------------------------------------------- + /// | header (5 bytes) | data (variable length) | + /// ------------------------------------------------------------------------- + /// + /// Where the header is + /// ------------------------------------------------ + /// | header (5 bytes) | + /// ------------------------------------------------ + /// | sentinel (1 byte) | length of data (4 bytes) | + /// ------------------------------------------------ + /// + /// The writing code will write out the header, 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 buffer, synchronous deserialization can happen without any 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. + /// + /// internal static class RemoteHostAssetSerialization { + /// + /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as possible. + /// + private const byte s_messageSentinelByte = 0b01010101; + private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); public static async ValueTask WriteDataAsync( @@ -47,6 +81,10 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat Contract.ThrowIfNull(asset); foundChecksumCount++; + // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect + // problems with our writing. + WriteSentinelByte(); + using var pooledObject = s_streamPool.GetPooledObject(); var tempStream = pooledObject.Object; tempStream.Position = 0; @@ -93,6 +131,13 @@ void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) } } + void WriteSentinelByte() + { + var span = pipeWriter.GetSpan(1); + span[0] = s_messageSentinelByte; + pipeWriter.Advance(1); + } + void WriteLengthToPipeWriter(long length) { Contract.ThrowIfTrue(length > int.MaxValue); @@ -122,12 +167,16 @@ static async ValueTask ReadDataSuppressedFlowAsync( for (var i = 0; i < objectCount; i++) { - // First, read the length of the data chunk we'll be reading. - var lengthReadResult = await pipeReader.ReadAtLeastAsync(sizeof(int), cancellationToken).ConfigureAwait(false); + // First, read the sentinel byte and the length of the data chunk we'll be reading. + const int HeaderSize = 1 + sizeof(int); + var lengthReadResult = await pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); + var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); + + // Check that the sentinel is correct. + Contract.ThrowIfTrue(sentinelByte != s_messageSentinelByte); - // read and advance past the length. - var length = ReadLength(lengthReadResult); - pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(sizeof(int))); + // If so, move the pipe reader forward to the end of the header. + pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); // 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 @@ -154,11 +203,12 @@ static async ValueTask ReadDataSuppressedFlowAsync( } } - static int ReadLength(ReadResult readResult) + 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 length; + return (sentinel, length); } } } From ee952a5d2c4787d6a864b1dbc58a3d78683c9c51 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:27:07 -0700 Subject: [PATCH 0371/1047] Docs, and remove stream --- .../Remote/Core/RemoteHostAssetSerialization.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index bf3d157ce4347..77d5b81eaa2f8 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -62,14 +62,12 @@ public static async ValueTask WriteDataAsync( ISerializerService serializer, CancellationToken cancellationToken) { - var pipeWriterStream = pipeWriter.AsStream(); - var foundChecksumCount = 0; await scope.AddAssetsAsync( assetPath, checksums, - WriteAssetToPipeAsync, + onAssetFoundAsync: WriteAssetToPipeAsync, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); @@ -91,18 +89,20 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat // 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.SetLength(0, truncate: false); // 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 fasion. + // Instead, we'll handle the pipe-writing ourselves afterwards in a completely async fashion. WriteAssetToTempStream(tempStream, checksum, asset); // Write the length of the asset to the pipe writer so the reader knows how much data to read. WriteLengthToPipeWriter(tempStream.Length); // Ensure we flush out the length so the reading side knows how much data to read. - await pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); + await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); // Now, asynchronously copy the temp buffer over to the writer stream. tempStream.Position = 0; @@ -112,7 +112,7 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat // 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 pipeWriterStream.FlushAsync(cancellationToken).ConfigureAwait(false); + await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); } void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) From 8c84b55182747f114cf04249d2313e0ef253071a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:32:42 -0700 Subject: [PATCH 0372/1047] Docs --- .../Remote/Core/RemoteHostAssetSerialization.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 77d5b81eaa2f8..215802e273425 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -44,6 +44,19 @@ namespace Microsoft.CodeAnalysis.Remote /// 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 (4 bytes) | asset-data (asset specified) | + /// ----------------------------------------------------------------------------------------------------------- + /// + /// The validation bytes are followed by the checksum. This 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 is used by the reading code to know which asset-deserialization routine to + /// invoke. Finally, the asset data itself is written out. /// internal static class RemoteHostAssetSerialization { @@ -101,7 +114,8 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat // Write the length of the asset to the pipe writer so the reader knows how much data to read. WriteLengthToPipeWriter(tempStream.Length); - // Ensure we flush out the length so the reading side knows how much data to read. + // Ensure we flush out the length so the reading side can immediately read the header to determine qhow + // 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. From 7453905c65411eac1013adf7f1361c3d2f1f2256 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:48:36 -0700 Subject: [PATCH 0373/1047] Cleanup --- .../Core/RemoteHostAssetSerialization.cs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 215802e273425..b9c10be3b0e00 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -21,20 +21,14 @@ namespace Microsoft.CodeAnalysis.Remote /// server. The format we use is as follows. For each asset we're writing we write: /// /// ------------------------------------------------------------------------- - /// | header (5 bytes) | data (variable length) | + /// | sentinel (1 byte) | length of data (4 bytes) | data (variable length) | /// ------------------------------------------------------------------------- - /// - /// Where the header is - /// ------------------------------------------------ - /// | header (5 bytes) | - /// ------------------------------------------------ - /// | sentinel (1 byte) | length of data (4 bytes) | - /// ------------------------------------------------ /// - /// The writing code will write out the header, 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 buffer, synchronous deserialization can happen without any blocking on async-io. + /// 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. @@ -53,17 +47,17 @@ namespace Microsoft.CodeAnalysis.Remote /// | ObjectWriter validation (2 bytes) | checksum (16 bytes) | kind (4 bytes) | asset-data (asset specified) | /// ----------------------------------------------------------------------------------------------------------- /// - /// The validation bytes are followed by the checksum. This 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 is used by the reading code to know which asset-deserialization routine to - /// invoke. Finally, the asset data itself is written out. + /// 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 static class RemoteHostAssetSerialization { /// /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as possible. /// - private const byte s_messageSentinelByte = 0b01010101; + private const byte MessageSentinelByte = 0b01010101; private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); @@ -148,7 +142,7 @@ void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) void WriteSentinelByte() { var span = pipeWriter.GetSpan(1); - span[0] = s_messageSentinelByte; + span[0] = MessageSentinelByte; pipeWriter.Advance(1); } @@ -187,7 +181,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); // Check that the sentinel is correct. - Contract.ThrowIfTrue(sentinelByte != s_messageSentinelByte); + Contract.ThrowIfTrue(sentinelByte != MessageSentinelByte); // If so, move the pipe reader forward to the end of the header. pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); From 3aa19c20c526e34b8507e5d211f24a00f2557e84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:54:29 -0700 Subject: [PATCH 0374/1047] Extract helper --- .../Core/RemoteHostAssetSerialization.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index b9c10be3b0e00..63cd329b1354d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -84,37 +84,32 @@ await scope.AddAssetsAsync( async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) { Contract.ThrowIfNull(asset); + foundChecksumCount++; // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect // problems with our writing. WriteSentinelByte(); - using var pooledObject = s_streamPool.GetPooledObject(); - var tempStream = pooledObject.Object; - tempStream.Position = 0; - - // 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.SetLength(0, truncate: false); - // 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, checksum, asset); - // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLengthToPipeWriter(tempStream.Length); + using (var _ = GetTempStream(out var tempStream)) + { + WriteAssetToTempStream(tempStream, checksum, asset); - // Ensure we flush out the length so the reading side can immediately read the header to determine qhow - // much data to it will need to prebuffer. - await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteLengthToPipeWriter(tempStream.Length); - // Now, asynchronously copy the temp buffer over to the writer stream. - tempStream.Position = 0; - await tempStream.CopyToAsync(pipeWriter, cancellationToken).ConfigureAwait(false); + // Ensure we flush out the length so the reading side can immediately read the header to determine qhow + // 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 @@ -154,6 +149,21 @@ void WriteLengthToPipeWriter(long length) BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); pipeWriter.Advance(sizeof(int)); } + + PooledObject GetTempStream(out Stream stream) + { + var pooledObject = s_streamPool.GetPooledObject(); + var tempStream = pooledObject.Object; + tempStream.Position = 0; + + // 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.SetLength(0, truncate: false); + stream = tempStream; + return pooledObject; + } } public static ValueTask ReadDataAsync( From dc27fa52a99e5a5b6dbd98d3e397514b15d9c2c9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:55:15 -0700 Subject: [PATCH 0375/1047] rename --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 63cd329b1354d..b755e27c480a8 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -100,7 +100,7 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat WriteAssetToTempStream(tempStream, checksum, asset); // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLengthToPipeWriter(tempStream.Length); + WriteLength(tempStream.Length); // Ensure we flush out the length so the reading side can immediately read the header to determine qhow // much data to it will need to prebuffer. @@ -141,7 +141,7 @@ void WriteSentinelByte() pipeWriter.Advance(1); } - void WriteLengthToPipeWriter(long length) + void WriteLength(long length) { Contract.ThrowIfTrue(length > int.MaxValue); From a088eb956355d60ef9ea5b3085dc653a03e81f22 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 19:58:13 -0700 Subject: [PATCH 0376/1047] Fix issue where the streaming service might return a different object than what is in the asset cache --- src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs index e754650d6939a..38839a93509f2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/AssetProvider.cs @@ -294,8 +294,11 @@ await RequestAssetsAsync( { var missingChecksum = tuple.missingChecksums[index]; + // 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!); - tuple.assetProvider._assetCache.GetOrAdd(missingChecksum, missingAsset!); }, (this, missingChecksums, callback, arg), cancellationToken).ConfigureAwait(false); From 3dd814c4606ea309ea29373e05304695c69207df Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 20:29:50 -0700 Subject: [PATCH 0377/1047] lint --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 30dc783288876..4791fbb3a6c04 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -469,7 +469,7 @@ public async Task FindAsync( 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."); ; + 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."); await onAssetFoundAsync(ParseOptions, parseOptions, cancellationToken).ConfigureAwait(false); } From 636a75ba148e8aec2b4377abfb6a82a6eac1e359 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 20:46:25 -0700 Subject: [PATCH 0378/1047] rename --- .../Remote/Core/RemoteHostAssetSerialization.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index b755e27c480a8..81608a3b4fc21 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -120,17 +120,17 @@ async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, Cancellat void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) { - using var objectWriter = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); + using var writer = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); { // Write the checksum for the asset we're writing out, so the other side knows what asset this is. - checksum.WriteTo(objectWriter); + checksum.WriteTo(writer); // Write out the kind so the receiving end knows how to deserialize this asset. var kind = asset.GetWellKnownSynchronizationKind(); - objectWriter.WriteInt32((int)kind); + writer.WriteInt32((int)kind); // Now serialize out the asset itself. - serializer.Serialize(asset, objectWriter, scope.ReplicationContext, cancellationToken); + serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); } } From 33c841480b0d4764914776c02db451bebb5e0b35 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 13 Apr 2024 20:52:25 -0700 Subject: [PATCH 0379/1047] Revert --- src/Workspaces/Remote/Core/SolutionAssetProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index a2c08b712fcdb..d4df3ec47e989 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -73,14 +73,13 @@ private async ValueTask WriteAssetsWorkerAsync( var serializer = _services.GetRequiredService(); var scope = assetStorage.GetScope(solutionChecksum); - using var stream = new PipeWriterStream(pipeWriter); - using var _ = Creator.CreateResultMap(out var resultMap); await scope.AddAssetsAsync(assetPath, checksums, resultMap, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); + using var stream = new PipeWriterStream(pipeWriter); await RemoteHostAssetSerialization.WriteDataAsync( stream, resultMap, serializer, scope.ReplicationContext, solutionChecksum, checksums, cancellationToken).ConfigureAwait(false); From 6d9a341b7f4b1323215ee5fbeed4184c7b198296 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 00:34:02 -0700 Subject: [PATCH 0380/1047] Switch to using a channel --- .../Workspace/Solution/ChecksumCollection.cs | 12 ++-- .../Workspace/Solution/StateChecksums.cs | 68 ++++++++----------- .../Core/RemoteHostAssetSerialization.cs | 64 +++++++++++++++-- .../Remote/Core/SolutionAssetStorage.Scope.cs | 13 ++-- .../Remote/ServiceHub/Host/TestUtils.cs | 12 +--- 5 files changed, 101 insertions(+), 68 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 001555ef464dd..4f4fa9d6287bf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -58,7 +58,7 @@ internal static async Task FindAsync( AssetPath assetPath, TextDocumentStates documentStates, HashSet searchingChecksumsLeft, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) where TState : TextDocumentState { var hintDocument = assetPath.DocumentId; @@ -69,7 +69,7 @@ internal static async Task FindAsync( { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); await stateChecksums.FindAsync( - assetPath, state, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } else @@ -83,16 +83,16 @@ await stateChecksums.FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); await stateChecksums.FindAsync( - assetPath, state, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } - internal static async Task FindAsync( + internal static void Find( IReadOnlyList values, ChecksumCollection checksums, HashSet searchingChecksumsLeft, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) where T : class { Contract.ThrowIfFalse(values.Count == checksums.Children.Length); @@ -105,7 +105,7 @@ internal static async Task FindAsync( var checksum = checksums.Children[i]; if (searchingChecksumsLeft.Remove(checksum)) - await onAssetFoundAsync(checksum, values[i], cancellationToken).ConfigureAwait(false); + onAssetFound(checksum, values[i]); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 4791fbb3a6c04..8ca24c637b837 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -118,7 +118,7 @@ public async Task FindAsync( ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -128,10 +128,10 @@ public async Task FindAsync( if (assetPath.IncludeSolutionCompilationState) { if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) - await onAssetFoundAsync(this.Checksum, this, cancellationToken).ConfigureAwait(false); + onAssetFound(this.Checksum, this); if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) - await onAssetFoundAsync(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap, cancellationToken).ConfigureAwait(false); + onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap); if (compilationState.FrozenSourceGeneratedDocumentStates != null) { @@ -143,7 +143,7 @@ public async Task FindAsync( { await ChecksumCollection.FindAsync( new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), - compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFound, 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 @@ -161,7 +161,7 @@ await ChecksumCollection.FindAsync( if (searchingChecksumsLeft.Remove(identityChecksum)) { Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); - await onAssetFoundAsync(identityChecksum, state.Identity, cancellationToken).ConfigureAwait(false); + onAssetFound(identityChecksum, state.Identity); } } } @@ -175,7 +175,7 @@ await ChecksumCollection.FindAsync( { var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - await onAssetFoundAsync(identityChecksum, state.Identity, cancellationToken).ConfigureAwait(false); + onAssetFound(identityChecksum, state.Identity); } } } @@ -190,14 +190,14 @@ await ChecksumCollection.FindAsync( // entire solution. Contract.ThrowIfFalse(solutionState.TryGetStateChecksums(out var solutionChecksums)); await solutionChecksums.FindAsync( - solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, 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, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -271,7 +271,7 @@ public async Task FindAsync( ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -281,16 +281,13 @@ public async Task FindAsync( if (assetPath.IncludeSolutionState) { if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - await onAssetFoundAsync(Checksum, this, cancellationToken).ConfigureAwait(false); + onAssetFound(Checksum, this); if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) - await onAssetFoundAsync(Attributes, solution.SolutionAttributes, cancellationToken).ConfigureAwait(false); + onAssetFound(Attributes, solution.SolutionAttributes); if (assetPath.IncludeSolutionAnalyzerReferences) - { - await ChecksumCollection.FindAsync( - solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); - } + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); } if (searchingChecksumsLeft.Count == 0) @@ -310,7 +307,7 @@ await ChecksumCollection.FindAsync( projectState.TryGetStateChecksums(out var projectStateChecksums)) { await projectStateChecksums.FindAsync( - projectState, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } else @@ -334,7 +331,7 @@ await projectStateChecksums.FindAsync( continue; await projectStateChecksums.FindAsync( - projectState, assetPath, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -442,7 +439,7 @@ public async Task FindAsync( ProjectState state, AssetPath assetPath, HashSet searchingChecksumsLeft, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -456,47 +453,38 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - await onAssetFoundAsync(Checksum, this, cancellationToken).ConfigureAwait(false); + onAssetFound(Checksum, this); if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) - await onAssetFoundAsync(Info, state.ProjectInfo.Attributes, cancellationToken).ConfigureAwait(false); + onAssetFound(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."); - await onAssetFoundAsync(CompilationOptions, compilationOptions, cancellationToken).ConfigureAwait(false); + onAssetFound(CompilationOptions, 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."); - await onAssetFoundAsync(ParseOptions, parseOptions, cancellationToken).ConfigureAwait(false); + onAssetFound(ParseOptions, parseOptions); } if (assetPath.IncludeProjectProjectReferences) - { - await ChecksumCollection.FindAsync( - state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); - } + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); if (assetPath.IncludeProjectMetadataReferences) - { - await ChecksumCollection.FindAsync( - state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); - } + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); if (assetPath.IncludeProjectAnalyzerReferences) - { - await ChecksumCollection.FindAsync( - state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); - } + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); } if (assetPath.IncludeDocuments) { - await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -522,7 +510,7 @@ public async Task FindAsync( AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) { Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum); @@ -530,12 +518,12 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) - await onAssetFoundAsync(Info, state.Attributes, cancellationToken).ConfigureAwait(false); + onAssetFound(Info, state.Attributes); if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) { var text = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); - await onAssetFoundAsync(Text, text, cancellationToken).ConfigureAwait(false); + onAssetFound(Text, text); } } } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 81608a3b4fc21..21621eb67b590 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -5,12 +5,17 @@ using System; using System.Buffers; using System.Buffers.Binary; +using System.Collections.Generic; using System.IO; using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using System.Security.AccessControl; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Extensions; using Nerdbank.Streams; using Roslyn.Utilities; @@ -61,6 +66,16 @@ internal static class RemoteHostAssetSerialization private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + private static async IAsyncEnumerable ReadAllAsync( + this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) + { + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (reader.TryRead(out var item)) + yield return item; + } + } + public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, AssetPath assetPath, @@ -71,12 +86,51 @@ public static async ValueTask WriteDataAsync( { var foundChecksumCount = 0; - await scope.AddAssetsAsync( - assetPath, - checksums, - onAssetFoundAsync: WriteAssetToPipeAsync, - cancellationToken).ConfigureAwait(false); + var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(new UnboundedChannelOptions() + { + AllowSynchronousContinuations = false, + SingleReader = true, + }); + + // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets + // to the pipe. + using var _ = cancellationToken.Register(() => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); + + // Spin up a task to go search for all the requested checksums, adding results to the channel. + var addAssetsTask = Task.Run(async () => + { + try + { + await scope.AddAssetsAsync( + assetPath, + checksums, + (checksum, asset) => channel.Writer.TryWrite((checksum, asset)), + cancellationToken).ConfigureAwait(false); + + // We finished searching for all the checksums, let the writer know. + channel.Writer.TryComplete(); + } + catch (Exception ex) + { + // If Something went wrong ensure that we complete the channel so that the writing task will stop. + // Also bubble the exception out so that the outer Task.WhenAll will bubble it up. + channel.Writer.TryComplete(ex); + throw; + } + }, cancellationToken); + + // Spin up a task to read from the channel and write out the assets to the pipe-writer. + var writeAssetsTask = Task.Run(async () => + { + await foreach (var (checksum, asset) in ReadAllAsync(channel.Reader, cancellationToken)) + await WriteAssetToPipeAsync(checksum, asset, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + + // Wait for both the searching and writing tasks to finish. + await Task.WhenAll(addAssetsTask, writeAssetsTask).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + // If we weren't canceled, we better have found and written out all the expected assets. Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); return; diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 8534d04622777..8f7e308439603 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -46,7 +46,7 @@ public void Dispose() public async Task AddAssetsAsync( AssetPath assetPath, ReadOnlyMemory checksums, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -57,7 +57,7 @@ public async Task AddAssetsAsync( var numberOfChecksumsToSearch = checksumsToFind.Count; Contract.ThrowIfTrue(checksumsToFind.Contains(Checksum.Null)); - await FindAssetsAsync(assetPath, checksumsToFind, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + await FindAssetsAsync(assetPath, checksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); } @@ -65,7 +65,7 @@ public async Task AddAssetsAsync( private async Task FindAssetsAsync( AssetPath assetPath, HashSet remainingChecksumsToFind, - Func onAssetFoundAsync, + Action onAssetFound, CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -75,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, assetPath, remainingChecksumsToFind, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, 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, assetPath, remainingChecksumsToFind, onAssetFoundAsync, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); } } @@ -101,12 +101,11 @@ public async ValueTask GetAssetAsync(Checksum checksum, CancellationToke using var checksumPool = Creator.CreateChecksumSet(checksum); object? asset = null; - await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksumPool.Object, (foundChecksum, foundAsset, _) => + await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksumPool.Object, (foundChecksum, foundAsset) => { Contract.ThrowIfTrue(asset != null); // We should only find one asset Contract.ThrowIfTrue(checksum != foundChecksum); asset = foundAsset; - return ValueTaskFactory.CompletedTask; }, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(asset); diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 5c48f36502841..4363e746ad586 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -184,11 +184,7 @@ public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { - var callback = (Checksum checksum, object asset, CancellationToken cancellationToken) => - { - map[checksum] = asset; - return ValueTaskFactory.CompletedTask; - }; + var callback = (Checksum checksum, object asset) => { map[checksum] = asset; }; if (projectId == null) { @@ -231,11 +227,7 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary - { - map[checksum] = asset; - return ValueTaskFactory.CompletedTask; - }; + var callback = (Checksum checksum, object asset) => { map[checksum] = asset; }; var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), callback, cancellationToken).ConfigureAwait(false); From 7a0f7ad1c2100319601254cd0080d20680563f7e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 00:39:49 -0700 Subject: [PATCH 0381/1047] File scoped namespace --- .../Core/RemoteHostAssetSerialization.cs | 459 +++++++++--------- .../Remote/Core/SolutionAssetStorage.Scope.cs | 2 +- 2 files changed, 236 insertions(+), 225 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 21621eb67b590..f4dadf7f90223 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -9,279 +9,290 @@ using System.IO; using System.IO.Pipelines; using System.Runtime.CompilerServices; -using System.Security.AccessControl; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Nerdbank.Streams; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// 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 (4 bytes) | 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 static class RemoteHostAssetSerialization { /// - /// 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 (4 bytes) | 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. + /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as possible. /// - internal static class RemoteHostAssetSerialization - { - /// - /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as possible. - /// - private const byte MessageSentinelByte = 0b01010101; + private const byte MessageSentinelByte = 0b01010101; - private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); - private static async IAsyncEnumerable ReadAllAsync( - this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) + private static async IAsyncEnumerable ReadAllAsync( + this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) + { + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (reader.TryRead(out var item)) - yield return item; - } + while (reader.TryRead(out var item)) + yield return item; } + } + + public static async ValueTask WriteDataAsync( + PipeWriter pipeWriter, + AssetPath assetPath, + ReadOnlyMemory checksums, + SolutionAssetStorage.Scope scope, + ISerializerService serializer, + CancellationToken cancellationToken) + { + var foundChecksumCount = 0; - public static async ValueTask WriteDataAsync( - PipeWriter pipeWriter, - AssetPath assetPath, - ReadOnlyMemory checksums, - SolutionAssetStorage.Scope scope, - ISerializerService serializer, - CancellationToken cancellationToken) + var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(new UnboundedChannelOptions() { - var foundChecksumCount = 0; + // We have a single task reading the data from the channel and writing it to the pipe. This option + // allows the channel to operate in a more efficient manner knowing it won't have to sychronize data + // for multiple readers. + SingleReader = true, - var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(new UnboundedChannelOptions() - { - AllowSynchronousContinuations = false, - SingleReader = true, - }); + // Currently we only have a single writer writing to the channel when we call FindAllAssetsAsync. + // However, we could change this in the future to allow the search to happen in parallel. + SingleWriter = true, + }); - // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets - // to the pipe. - using var _ = cancellationToken.Register(() => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); + // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets + // to the pipe. + using var _ = cancellationToken.Register(() => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); - // Spin up a task to go search for all the requested checksums, adding results to the channel. - var addAssetsTask = Task.Run(async () => - { - try - { - await scope.AddAssetsAsync( - assetPath, - checksums, - (checksum, asset) => channel.Writer.TryWrite((checksum, asset)), - cancellationToken).ConfigureAwait(false); - - // We finished searching for all the checksums, let the writer know. - channel.Writer.TryComplete(); - } - catch (Exception ex) - { - // If Something went wrong ensure that we complete the channel so that the writing task will stop. - // Also bubble the exception out so that the outer Task.WhenAll will bubble it up. - channel.Writer.TryComplete(ex); - throw; - } - }, cancellationToken); - - // Spin up a task to read from the channel and write out the assets to the pipe-writer. - var writeAssetsTask = Task.Run(async () => - { - await foreach (var (checksum, asset) in ReadAllAsync(channel.Reader, cancellationToken)) - await WriteAssetToPipeAsync(checksum, asset, cancellationToken).ConfigureAwait(false); - }, cancellationToken); + // Spin up a task to go search for all the requested checksums, adding results to the channel. + var findAssetsTask = FindAllAssetsAsync(); - // Wait for both the searching and writing tasks to finish. - await Task.WhenAll(addAssetsTask, writeAssetsTask).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + // Spin up a task to read from the channel and write out the assets to the pipe-writer. + var writeAssetsTask = WriteAllAssetsToPipeAsync(); - // If we weren't canceled, we better have found and written out all the expected assets. - Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); + // Wait for both the searching and writing tasks to finish. + await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - return; + // If we weren't canceled, we better have found and written out all the expected assets. + Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); + + return; + + async Task FindAllAssetsAsync() + { + await Task.Yield(); - async ValueTask WriteAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) + try { - Contract.ThrowIfNull(asset); + await scope.FindAssetsAsync( + assetPath, + checksums, + (checksum, asset) => channel.Writer.TryWrite((checksum, asset)), + cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + // If Something went wrong ensure that we complete the channel so that the writing task will stop. + // Also bubble the exception out so that the outer Task.WhenAll will bubble it up. + channel.Writer.TryComplete(ex); + throw; + } + finally + { + // We finished searching for all the checksums, let the writer know. + channel.Writer.TryComplete(); + } + } - foundChecksumCount++; + async Task WriteAllAssetsToPipeAsync() + { + await Task.Yield(); + await foreach (var (checksum, asset) in ReadAllAsync(channel.Reader, cancellationToken)) + await WriteSingleAssetToPipeAsync(checksum, asset, cancellationToken).ConfigureAwait(false); + } - // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect - // problems with our writing. - WriteSentinelByte(); + async ValueTask WriteSingleAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(asset); - // 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. + foundChecksumCount++; - using (var _ = GetTempStream(out var tempStream)) - { - WriteAssetToTempStream(tempStream, checksum, asset); + // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect + // problems with our writing. + WriteSentinelByte(); - // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLength(tempStream.Length); + // 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. - // Ensure we flush out the length so the reading side can immediately read the header to determine qhow - // much data to it will need to prebuffer. - await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + using (var _ = GetTempStream(out var tempStream)) + { + WriteAssetToTempStream(tempStream, checksum, asset); - // Now, asynchronously copy the temp buffer over to the writer stream. - tempStream.Position = 0; - await tempStream.CopyToAsync(pipeWriter, cancellationToken).ConfigureAwait(false); - } + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteLength(tempStream.Length); - // 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. + // Ensure we flush out the length so the reading side can immediately read the header to determine qhow + // much data to it will need to prebuffer. await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); - } - void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) - { - using var writer = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); - { - // Write the checksum for the asset we're writing out, so the other side knows what asset this is. - checksum.WriteTo(writer); - - // Write out the kind so the receiving end knows how to deserialize this asset. - var kind = asset.GetWellKnownSynchronizationKind(); - writer.WriteInt32((int)kind); - - // Now serialize out the asset itself. - serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); - } + // Now, asynchronously copy the temp buffer over to the writer stream. + tempStream.Position = 0; + await tempStream.CopyToAsync(pipeWriter, cancellationToken).ConfigureAwait(false); } - void WriteSentinelByte() - { - var span = pipeWriter.GetSpan(1); - span[0] = MessageSentinelByte; - pipeWriter.Advance(1); - } + // 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); + } - void WriteLength(long length) + void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) + { + using var writer = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); { - Contract.ThrowIfTrue(length > int.MaxValue); + // Write the checksum for the asset we're writing out, so the other side knows what asset this is. + checksum.WriteTo(writer); + + // Write out the kind so the receiving end knows how to deserialize this asset. + var kind = asset.GetWellKnownSynchronizationKind(); + writer.WriteInt32((int)kind); - var span = pipeWriter.GetSpan(sizeof(int)); - BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); - pipeWriter.Advance(sizeof(int)); + // Now serialize out the asset itself. + serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); } + } - PooledObject GetTempStream(out Stream stream) - { - var pooledObject = s_streamPool.GetPooledObject(); - var tempStream = pooledObject.Object; - tempStream.Position = 0; + void WriteSentinelByte() + { + var span = pipeWriter.GetSpan(1); + span[0] = MessageSentinelByte; + pipeWriter.Advance(1); + } - // 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.SetLength(0, truncate: false); - stream = tempStream; - return pooledObject; - } + void WriteLength(long length) + { + Contract.ThrowIfTrue(length > int.MaxValue); + + var span = pipeWriter.GetSpan(sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); + pipeWriter.Advance(sizeof(int)); + } + + PooledObject GetTempStream(out Stream stream) + { + var pooledObject = s_streamPool.GetPooledObject(); + var tempStream = pooledObject.Object; + tempStream.Position = 0; + + // 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.SetLength(0, truncate: false); + stream = tempStream; + return pooledObject; } + } - public static ValueTask ReadDataAsync( + public static ValueTask ReadDataAsync( + PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, 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, objectCount, serializerService, callback, arg, cancellationToken); + + static async ValueTask ReadDataSuppressedFlowAsync( PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, 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, objectCount, serializerService, callback, arg, cancellationToken); - - static async ValueTask ReadDataSuppressedFlowAsync( - PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) - { - using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); - - for (var i = 0; i < objectCount; i++) - { - // First, read the sentinel byte and the length of the data chunk we'll be reading. - const int HeaderSize = 1 + sizeof(int); - var lengthReadResult = await pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); - var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); - - // Check that the sentinel is correct. - Contract.ThrowIfTrue(sentinelByte != MessageSentinelByte); - - // If so, move the pipe reader forward to the end of the header. - pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); - - // 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 - // from within ObjectReader.GetReader below. - pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - - // 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. - using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, cancellationToken); - - var checksum = Checksum.ReadFrom(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); - - // in service hub, cancellation means simply closed stream - var result = serializerService.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(result); - callback(checksum, (T)result, arg); - } - } + using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); - static (byte, int) ReadSentinelAndLength(ReadResult readResult) + for (var i = 0; i < objectCount; i++) { - var sequenceReader = new SequenceReader(readResult.Buffer); - Contract.ThrowIfFalse(sequenceReader.TryRead(out var sentinel)); - Contract.ThrowIfFalse(sequenceReader.TryReadLittleEndian(out int length)); - return (sentinel, length); + // First, read the sentinel byte and the length of the data chunk we'll be reading. + const int HeaderSize = 1 + sizeof(int); + var lengthReadResult = await pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); + var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); + + // Check that the sentinel is correct. + Contract.ThrowIfTrue(sentinelByte != MessageSentinelByte); + + // If so, move the pipe reader forward to the end of the header. + pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); + + // 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 + // from within ObjectReader.GetReader below. + pipeReader.AdvanceTo(fillReadResult.Buffer.Start); + + // 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. + using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, cancellationToken); + + var checksum = Checksum.ReadFrom(reader); + var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); + + // in service hub, cancellation means simply closed stream + var result = serializerService.Deserialize(kind, reader, cancellationToken); + Contract.ThrowIfNull(result); + callback(checksum, (T)result, arg); } } + + 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); + } } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 8f7e308439603..58192bf103216 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -43,7 +43,7 @@ public void Dispose() /// Retrieve assets of specified available within from /// the storage. /// - public async Task AddAssetsAsync( + public async Task FindAssetsAsync( AssetPath assetPath, ReadOnlyMemory checksums, Action onAssetFound, From b7499d2406941dbac30b1b624b6f2dbfdfaddd67 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 00:57:25 -0700 Subject: [PATCH 0382/1047] revert --- .../Core/Portable/Workspace/Solution/ChecksumCollection.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 4f4fa9d6287bf..41fcd81127cef 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -68,8 +68,7 @@ internal static async Task FindAsync( if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync( - assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } else @@ -82,8 +81,7 @@ await stateChecksums.FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync( - assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } From 690dcd6c47fadfa81ffea6f392ff078058b4fca1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 00:57:53 -0700 Subject: [PATCH 0383/1047] revert --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 8ca24c637b837..69b935928c7a5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -189,15 +189,13 @@ await ChecksumCollection.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, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, 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, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } From 98d0600afe50bff1edc7b6d30506f6f194436510 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 00:58:19 -0700 Subject: [PATCH 0384/1047] revert --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 69b935928c7a5..2a532f588897c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -304,8 +304,7 @@ public async Task FindAsync( if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) { - await projectStateChecksums.FindAsync( - projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } else @@ -328,8 +327,7 @@ await projectStateChecksums.FindAsync( if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) continue; - await projectStateChecksums.FindAsync( - projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } From 60c485786c02b6c01339b9423f7181d5b0362ddb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 00:59:34 -0700 Subject: [PATCH 0385/1047] revert --- src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 58192bf103216..2de88bc4510ae 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -63,10 +63,7 @@ public async Task FindAssetsAsync( } private async Task FindAssetsAsync( - AssetPath assetPath, - HashSet remainingChecksumsToFind, - Action onAssetFound, - CancellationToken cancellationToken) + AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, CancellationToken cancellationToken) { var solutionState = this.CompilationState; From a1a01a6733d0796a71603e131d6733a6b5966ba9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 01:03:54 -0700 Subject: [PATCH 0386/1047] simplify --- .../Remote/Core/RemoteHostAssetSerialization.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index f4dadf7f90223..8fbc6b3299dea 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -81,8 +81,9 @@ public static async ValueTask WriteDataAsync( ISerializerService serializer, CancellationToken cancellationToken) { - var foundChecksumCount = 0; - + // Create a channel to communicate between the searching and writing tasks. This allows the searching task to + // find items, add them to the channel synchronously, and immediately continue searching for more items. + // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(new UnboundedChannelOptions() { // We have a single task reading the data from the channel and writing it to the pipe. This option @@ -99,6 +100,9 @@ public static async ValueTask WriteDataAsync( // to the pipe. using var _ = cancellationToken.Register(() => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); + // Keep track of how many checksums we found. We must find all the checksums we were asked to find. + var foundChecksumCount = 0; + // Spin up a task to go search for all the requested checksums, adding results to the channel. var findAssetsTask = FindAllAssetsAsync(); From 1398dd0eecf2fcf411f482739fe81304b5a852ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 01:04:52 -0700 Subject: [PATCH 0387/1047] Inline --- .../Core/RemoteHostAssetSerialization.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 8fbc6b3299dea..b1bb9d8c887a9 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -63,16 +63,6 @@ internal static class RemoteHostAssetSerialization private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); - private static async IAsyncEnumerable ReadAllAsync( - this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) - { - while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (reader.TryRead(out var item)) - yield return item; - } - } - public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, AssetPath assetPath, @@ -147,8 +137,12 @@ await scope.FindAssetsAsync( async Task WriteAllAssetsToPipeAsync() { await Task.Yield(); - await foreach (var (checksum, asset) in ReadAllAsync(channel.Reader, cancellationToken)) - await WriteSingleAssetToPipeAsync(checksum, asset, cancellationToken).ConfigureAwait(false); + + while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (channel.Reader.TryRead(out var item)) + await WriteSingleAssetToPipeAsync(item.checksum, item.asset, cancellationToken).ConfigureAwait(false); + } } async ValueTask WriteSingleAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) From 71256e407d6bbb5771813d44f4ad690c0fc4fd84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 01:07:23 -0700 Subject: [PATCH 0388/1047] Cleanup --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index b1bb9d8c887a9..bfaf74eb5117b 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -5,10 +5,8 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; using System.IO.Pipelines; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; From 3a1649321860f2deb17792fdefede47331f8264b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 01:08:16 -0700 Subject: [PATCH 0389/1047] Cleanup --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index bfaf74eb5117b..412ac90592f5e 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -248,7 +248,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( for (var i = 0; i < objectCount; i++) { // First, read the sentinel byte and the length of the data chunk we'll be reading. - const int HeaderSize = 1 + sizeof(int); + const int HeaderSize = sizeof(byte) + sizeof(int); var lengthReadResult = await pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); From 66f35775c21b63fe70cb71ce3b208fd1ee09c84c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 01:25:49 -0700 Subject: [PATCH 0390/1047] Switch to a callback style in the asset finding code --- .../Workspace/Solution/ChecksumCollection.cs | 10 +-- .../Workspace/Solution/StateChecksums.cs | 65 +++++++++++-------- .../Remote/Core/SolutionAssetStorage.Scope.cs | 15 +++-- .../Remote/ServiceHub/Host/TestUtils.cs | 20 ++++-- 4 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index e2a2d6fef2a46..41fcd81127cef 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -58,7 +58,7 @@ internal static async Task FindAsync( AssetPath assetPath, TextDocumentStates documentStates, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, CancellationToken cancellationToken) where TState : TextDocumentState { var hintDocument = assetPath.DocumentId; @@ -68,7 +68,7 @@ internal static async Task FindAsync( if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } else @@ -81,7 +81,7 @@ internal static async Task FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -90,7 +90,7 @@ internal static void Find( IReadOnlyList values, ChecksumCollection checksums, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, CancellationToken cancellationToken) where T : class { Contract.ThrowIfFalse(values.Count == checksums.Children.Length); @@ -103,7 +103,7 @@ internal static void Find( var checksum = checksums.Children[i]; if (searchingChecksumsLeft.Remove(checksum)) - result[checksum] = values[i]; + onAssetFound(checksum, values[i]); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 7762b7c92f61f..2a532f588897c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -118,7 +118,7 @@ public async Task FindAsync( ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -128,10 +128,10 @@ public async Task FindAsync( if (assetPath.IncludeSolutionCompilationState) { if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) - result[this.Checksum] = this; + onAssetFound(this.Checksum, this); if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) - result[this.SourceGeneratorExecutionVersionMap] = compilationState.SourceGeneratorExecutionVersionMap; + onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap); if (compilationState.FrozenSourceGeneratedDocumentStates != null) { @@ -143,7 +143,7 @@ public async Task FindAsync( { await ChecksumCollection.FindAsync( new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), - compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFound, 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 @@ -161,7 +161,7 @@ await ChecksumCollection.FindAsync( if (searchingChecksumsLeft.Remove(identityChecksum)) { Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); - result[identityChecksum] = state.Identity; + onAssetFound(identityChecksum, state.Identity); } } } @@ -175,7 +175,7 @@ await ChecksumCollection.FindAsync( { var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - result[identityChecksum] = state.Identity; + onAssetFound(identityChecksum, state.Identity); } } } @@ -189,13 +189,13 @@ await ChecksumCollection.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, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, 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, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -269,7 +269,7 @@ public async Task FindAsync( ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -279,13 +279,13 @@ public async Task FindAsync( if (assetPath.IncludeSolutionState) { if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; + onAssetFound(Checksum, this); if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) - result[Attributes] = solution.SolutionAttributes; + onAssetFound(Attributes, solution.SolutionAttributes); if (assetPath.IncludeSolutionAnalyzerReferences) - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); } if (searchingChecksumsLeft.Count == 0) @@ -304,7 +304,7 @@ public async Task FindAsync( if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) { - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } else @@ -327,7 +327,7 @@ public async Task FindAsync( if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) continue; - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -435,7 +435,7 @@ public async Task FindAsync( ProjectState state, AssetPath assetPath, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -449,32 +449,38 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - result[Checksum] = this; + onAssetFound(Checksum, this); if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) - result[Info] = state.ProjectInfo.Attributes; + onAssetFound(Info, state.ProjectInfo.Attributes); if (assetPath.IncludeProjectCompilationOptions && searchingChecksumsLeft.Remove(CompilationOptions)) - result[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."); + { + 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); + } if (assetPath.IncludeProjectParseOptions && searchingChecksumsLeft.Remove(ParseOptions)) - result[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."); + { + 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); + } if (assetPath.IncludeProjectProjectReferences) - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); if (assetPath.IncludeProjectMetadataReferences) - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); if (assetPath.IncludeProjectAnalyzerReferences) - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); } if (assetPath.IncludeDocuments) { - await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, result, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); } } } @@ -500,7 +506,7 @@ public async Task FindAsync( AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, - Dictionary result, + Action onAssetFound, CancellationToken cancellationToken) { Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum); @@ -508,10 +514,13 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) - result[Info] = state.Attributes; + onAssetFound(Info, state.Attributes); if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) - result[Text] = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); + { + var text = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); + onAssetFound(Text, text); + } } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index e4b6ae9f169a8..376028765c57a 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -58,14 +58,17 @@ public async Task AddAssetsAsync( var numberOfChecksumsToSearch = checksumsToFind.Count; Contract.ThrowIfTrue(checksumsToFind.Contains(Checksum.Null)); - await FindAssetsAsync(assetPath, checksumsToFind, assetMap, cancellationToken).ConfigureAwait(false); + await FindAssetsAsync( + assetPath, checksumsToFind, + (checksum, asset) => assetMap[checksum] = asset, + cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); Contract.ThrowIfTrue(assetMap.Count != numberOfChecksumsToSearch); } private async Task FindAssetsAsync( - AssetPath assetPath, HashSet remainingChecksumsToFind, Dictionary result, CancellationToken cancellationToken) + AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -74,13 +77,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, assetPath, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, 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, assetPath, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); } } @@ -97,10 +100,10 @@ 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); - await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksumPool.Object, resultPool, cancellationToken).ConfigureAwait(false); + var checksums = new ReadOnlyMemory([checksum]); + await scope.AddAssetsAsync(AssetPath.FullLookupForTesting, checksums, resultPool, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(resultPool.Count != 1); var (resultingChecksum, value) = resultPool.First(); diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 90a03a7481fc5..df05161be65cf 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -9,6 +9,8 @@ using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; +using ICSharpCode.Decompiler.IL; + #if DEBUG using System.Diagnostics; @@ -184,20 +186,22 @@ public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { + var callback = (Checksum checksum, object asset) => { map[checksum] = asset; }; + if (projectId == null) { var compilationChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(documentChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone: null, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), callback, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -205,11 +209,11 @@ public static async Task AppendAssetMapAsync( else { var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), map, cancellationToken).ConfigureAwait(false); + await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), callback, 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, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState.SolutionState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), callback, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); @@ -225,13 +229,15 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary { map[checksum] = asset; }; + var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), map, cancellationToken).ConfigureAwait(false); + await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), callback, 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(AssetPathKind.Documents, document.State, Flatten(documentChecksums), map, cancellationToken).ConfigureAwait(false); + await documentChecksums.FindAsync(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, cancellationToken).ConfigureAwait(false); } } From dda41def13ccc6d609f63bffbba2cc1978bbe3ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 01:27:10 -0700 Subject: [PATCH 0391/1047] Remove --- src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index df05161be65cf..4363e746ad586 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -9,8 +9,6 @@ using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -using ICSharpCode.Decompiler.IL; - #if DEBUG using System.Diagnostics; From 609deb7e788ae70469f8c8d88bee561cc6dc0db9 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 14 Apr 2024 12:18:31 +0000 Subject: [PATCH 0392/1047] Update dependencies from https://github.com/dotnet/source-build-externals build 20240411.1 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 9.0.0-alpha.1.24208.1 -> To Version 9.0.0-alpha.1.24211.1 From 147f802dce48cbde79a4692f2d6846eefdb7fe1a Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 14 Apr 2024 12:19:08 +0000 Subject: [PATCH 0393/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240409.3 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520903 From 6a1183a079731066399f80c0eb1ade7511f2d2a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:07:15 -0700 Subject: [PATCH 0394/1047] remove unused methods --- src/Workspaces/Core/Portable/Serialization/PooledList.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs index 672612efae0a6..da726b7a2e110 100644 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ b/src/Workspaces/Core/Portable/Serialization/PooledList.cs @@ -25,13 +25,6 @@ public static PooledObject> CreateChecksumSet(ReadOnlyMemory> CreateChecksumSet(Checksum checksum) - { - var items = SharedPools.Default>().GetPooledObject(); - items.Object.Add(checksum); - return items; - } - public static PooledObject> CreateList() => SharedPools.Default>().GetPooledObject(); From 5c93961c314c9f30f4647c90733c8d092eef8617 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:08:43 -0700 Subject: [PATCH 0395/1047] remove unused methods --- src/Workspaces/Core/Portable/Serialization/PooledList.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs index da726b7a2e110..79b86cc6f8fd9 100644 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ b/src/Workspaces/Core/Portable/Serialization/PooledList.cs @@ -27,11 +27,4 @@ public static PooledObject> CreateChecksumSet(ReadOnlyMemory> CreateList() => SharedPools.Default>().GetPooledObject(); - - public static PooledObject> CreateResultMap(out Dictionary result) - { - var pooled = SharedPools.Default>().GetPooledObject(); - result = pooled.Object; - return pooled; - } } From f85bb56ef8a82bd2f257ebc46a8375c43a713fca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:11:09 -0700 Subject: [PATCH 0396/1047] move method --- .../Remote/Core/SolutionAssetStorage.Scope.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 753ab871e787f..b558446686f57 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -51,8 +51,8 @@ public async Task FindAssetsAsync( { 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)); @@ -60,6 +60,14 @@ public async Task FindAssetsAsync( await FindAssetsAsync(assetPath, checksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); + + return; + + static void AddChecksums(ReadOnlyMemory checksums, HashSet checksumsToFind) + { + foreach (var checksum in checksums.Span) + checksumsToFind.Add(checksum); + } } private async Task FindAssetsAsync( From 3e34e7762f070073a09461b34bd008682bd1754d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:16:01 -0700 Subject: [PATCH 0397/1047] Make enum a single byte --- .../Remote/WellKnownSynchronizationKind.cs | 30 +++++++++---------- .../Core/Portable/Serialization/PooledList.cs | 11 ------- .../Core/RemoteHostAssetSerialization.cs | 14 ++++----- 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs index e54361ae4ebd0..a885afd3a2e73 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,22 +12,22 @@ internal enum WellKnownSynchronizationKind SolutionCompilationState = 1, // Solution snapshot state, only referencing actual user (non-generated) documents, options, and references. - SolutionState, - ProjectState, + SolutionState = 2, + ProjectState = 3, - ChecksumCollection, + ChecksumCollection = 4, - SolutionAttributes, - ProjectAttributes, - DocumentAttributes, - SourceGeneratedDocumentIdentity, - SourceGeneratorExecutionVersionMap, + SolutionAttributes = 5, + ProjectAttributes = 6, + DocumentAttributes = 7, + SourceGeneratedDocumentIdentity = 8, + SourceGeneratorExecutionVersionMap = 9, - CompilationOptions, - ParseOptions, - ProjectReference, - MetadataReference, - AnalyzerReference, + CompilationOptions = 10, + ParseOptions = 11, + ProjectReference = 12, + MetadataReference = 13, + AnalyzerReference = 14, - SerializableSourceText, + SerializableSourceText = 15, } diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs index 79b86cc6f8fd9..3eb2680bb6702 100644 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ b/src/Workspaces/Core/Portable/Serialization/PooledList.cs @@ -14,17 +14,6 @@ namespace Microsoft.CodeAnalysis.Serialization; /// 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> CreateList() => SharedPools.Default>().GetPooledObject(); } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 412ac90592f5e..88cc168c7afc7 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -41,11 +41,11 @@ namespace Microsoft.CodeAnalysis.Remote; /// 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 (4 bytes) | asset-data (asset specified) | -/// ----------------------------------------------------------------------------------------------------------- +/// ---------------------------------------------------------------------------------------------------------- +/// | 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). @@ -189,7 +189,7 @@ void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) // Write out the kind so the receiving end knows how to deserialize this asset. var kind = asset.GetWellKnownSynchronizationKind(); - writer.WriteInt32((int)kind); + writer.WriteByte((byte)kind); // Now serialize out the asset itself. serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); @@ -274,7 +274,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, cancellationToken); var checksum = Checksum.ReadFrom(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); + var kind = (WellKnownSynchronizationKind)reader.ReadByte(); // in service hub, cancellation means simply closed stream var result = serializerService.Deserialize(kind, reader, cancellationToken); From fbe99f2c7f03f296ef3b79c88dbf3b87f3fb704c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:17:49 -0700 Subject: [PATCH 0398/1047] Make enum a single byte --- .../Remote/WellKnownSynchronizationKind.cs | 36 +++++++++---------- .../Serialization/SerializationExtensions.cs | 1 - .../Serialization/SerializerService.cs | 5 --- .../Core/RemoteHostAssetSerialization.cs | 4 +-- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs b/src/Workspaces/Core/Portable/Remote/WellKnownSynchronizationKind.cs index e54361ae4ebd0..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,22 +12,20 @@ internal enum WellKnownSynchronizationKind SolutionCompilationState = 1, // Solution snapshot state, only referencing actual user (non-generated) documents, options, and references. - SolutionState, - ProjectState, - - ChecksumCollection, - - SolutionAttributes, - ProjectAttributes, - DocumentAttributes, - SourceGeneratedDocumentIdentity, - SourceGeneratorExecutionVersionMap, - - CompilationOptions, - ParseOptions, - ProjectReference, - MetadataReference, - AnalyzerReference, - - 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/Serialization/SerializationExtensions.cs b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs index 07b6f5853642b..3a313639e206a 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializationExtensions.cs @@ -18,7 +18,6 @@ public static WellKnownSynchronizationKind GetWellKnownSynchronizationKind(this SolutionCompilationStateChecksums => WellKnownSynchronizationKind.SolutionCompilationState, SolutionStateChecksums => WellKnownSynchronizationKind.SolutionState, ProjectStateChecksums => WellKnownSynchronizationKind.ProjectState, - ChecksumCollection => WellKnownSynchronizationKind.ChecksumCollection, SolutionInfo.SolutionAttributes => WellKnownSynchronizationKind.SolutionAttributes, ProjectInfo.ProjectAttributes => WellKnownSynchronizationKind.ProjectAttributes, DocumentInfo.DocumentAttributes => WellKnownSynchronizationKind.DocumentAttributes, diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 226165867e859..9d94bccc485e8 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -157,10 +157,6 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont ((ProjectStateChecksums)value).Serialize(writer); return; - case WellKnownSynchronizationKind.ChecksumCollection: - ((ChecksumCollection)value).WriteTo(writer); - return; - case WellKnownSynchronizationKind.SourceGeneratorExecutionVersionMap: ((SourceGeneratorExecutionVersionMap)value).WriteTo(writer); return; @@ -184,7 +180,6 @@ public object Deserialize(WellKnownSynchronizationKind kind, ObjectReader reader WellKnownSynchronizationKind.SolutionCompilationState => SolutionCompilationStateChecksums.Deserialize(reader), WellKnownSynchronizationKind.SolutionState => SolutionStateChecksums.Deserialize(reader), WellKnownSynchronizationKind.ProjectState => ProjectStateChecksums.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), diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index a317e756f450a..38674b010a948 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -45,7 +45,7 @@ public static async ValueTask WriteDataAsync( var kind = asset.GetWellKnownSynchronizationKind(); checksum.WriteTo(writer); - writer.WriteInt32((int)kind); + writer.WriteByte((byte)kind); serializer.Serialize(asset, writer, context, cancellationToken); // We flush after each item as that forms a reasonably sized chunk of data to want to then send over the @@ -89,7 +89,7 @@ public static void ReadData( for (int i = 0, n = objectCount; i < n; i++) { var checksum = Checksum.ReadFrom(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadInt32(); + var kind = (WellKnownSynchronizationKind)reader.ReadByte(); // in service hub, cancellation means simply closed stream var result = serializerService.Deserialize(kind, reader, cancellationToken); From 3feaecde7102f88e6f03b43db218681db74ef365 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:25:47 -0700 Subject: [PATCH 0399/1047] fix tests --- .../Core/Test.Next/Remote/SerializationValidator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index c7f9578ffe043..77cd8df2ccb65 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -55,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 @@ -120,7 +120,7 @@ public async Task GetSolutionAsync(SolutionAssetStorage.Scope scope) } public ChecksumObjectCollection ToProjectObjects(ChecksumCollection collection) - => new(this, collection); + => new(this, WellKnownSynchronizationKind.ProjectState, collection); internal async Task VerifyAssetAsync(SolutionStateChecksums solutionObject) { From 1543c57572e4d46a78a80c80a047518824bc7be2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:27:50 -0700 Subject: [PATCH 0400/1047] different value --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 88cc168c7afc7..746f17ce21cf7 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -55,9 +55,11 @@ namespace Microsoft.CodeAnalysis.Remote; internal static class RemoteHostAssetSerialization { /// - /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as possible. + /// 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. /// - private const byte MessageSentinelByte = 0b01010101; + private const byte MessageSentinelByte = 0b10010000; private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); From 75ebbabf5472037aa20e5b2d04db2537470bb426 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:29:00 -0700 Subject: [PATCH 0401/1047] Singleton options --- .../Core/RemoteHostAssetSerialization.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 746f17ce21cf7..fce33fbb31ee3 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -63,6 +63,18 @@ internal static class RemoteHostAssetSerialization private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + private static readonly UnboundedChannelOptions s_channelOptions = new() + { + // We have a single task reading the data from the channel and writing it to the pipe. This option + // allows the channel to operate in a more efficient manner knowing it won't have to sychronize data + // for multiple readers. + SingleReader = true, + + // Currently we only have a single writer writing to the channel when we call FindAllAssetsAsync. + // However, we could change this in the future to allow the search to happen in parallel. + SingleWriter = true, + }; + public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, AssetPath assetPath, @@ -74,17 +86,7 @@ public static async ValueTask WriteDataAsync( // Create a channel to communicate between the searching and writing tasks. This allows the searching task to // find items, add them to the channel synchronously, and immediately continue searching for more items. // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. - var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(new UnboundedChannelOptions() - { - // We have a single task reading the data from the channel and writing it to the pipe. This option - // allows the channel to operate in a more efficient manner knowing it won't have to sychronize data - // for multiple readers. - SingleReader = true, - - // Currently we only have a single writer writing to the channel when we call FindAllAssetsAsync. - // However, we could change this in the future to allow the search to happen in parallel. - SingleWriter = true, - }); + var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(s_channelOptions); // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets // to the pipe. From 550043b781a5e72cf5fb7e67dab13790a46e9c20 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:32:19 -0700 Subject: [PATCH 0402/1047] less captures --- .../Remote/Core/RemoteHostAssetSerialization.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index fce33fbb31ee3..c45fcedd0cf4c 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -89,8 +89,15 @@ public static async ValueTask WriteDataAsync( var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(s_channelOptions); // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets - // to the pipe. - using var _ = cancellationToken.Register(() => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); + // to the pipe. Capture-free version is only available on netcore unfortunately. +#if NET + using var _ = cancellationToken.Register( + static (obj, cancellationToken) => ((Channel<(Checksum, object)>)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), + state: channel); +#else + using var _ = cancellationToken.Register( + () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); +#endif // Keep track of how many checksums we found. We must find all the checksums we were asked to find. var foundChecksumCount = 0; From 7d06a918dd215b39c25b51f03ad3cc4d4be1f219 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:34:20 -0700 Subject: [PATCH 0403/1047] make static --- .../Remote/Core/RemoteHostAssetSerialization.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index c45fcedd0cf4c..3451aca8dc08b 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -16,6 +16,8 @@ namespace Microsoft.CodeAnalysis.Remote; +using ChecksumChannel = Channel<(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: @@ -92,7 +94,7 @@ public static async ValueTask WriteDataAsync( // to the pipe. Capture-free version is only available on netcore unfortunately. #if NET using var _ = cancellationToken.Register( - static (obj, cancellationToken) => ((Channel<(Checksum, object)>)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), + static (obj, cancellationToken) => ((ChecksumChannel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), state: channel); #else using var _ = cancellationToken.Register( @@ -103,7 +105,7 @@ public static async ValueTask WriteDataAsync( var foundChecksumCount = 0; // Spin up a task to go search for all the requested checksums, adding results to the channel. - var findAssetsTask = FindAllAssetsAsync(); + var findAssetsTask = FindAllAssetsAsync(assetPath, checksums, scope, channel, cancellationToken); // Spin up a task to read from the channel and write out the assets to the pipe-writer. var writeAssetsTask = WriteAllAssetsToPipeAsync(); @@ -117,7 +119,12 @@ public static async ValueTask WriteDataAsync( return; - async Task FindAllAssetsAsync() + static async Task FindAllAssetsAsync( + AssetPath assetPath, + ReadOnlyMemory checksums, + SolutionAssetStorage.Scope scope, + ChecksumChannel channel, + CancellationToken cancellationToken) { await Task.Yield(); From d60ad9dc9acda19ba8b72e837c251572e8f030b7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:39:33 -0700 Subject: [PATCH 0404/1047] make local functions static --- .../Core/RemoteHostAssetSerialization.cs | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 3451aca8dc08b..e56593436c4ed 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -101,21 +101,15 @@ public static async ValueTask WriteDataAsync( () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); #endif - // Keep track of how many checksums we found. We must find all the checksums we were asked to find. - var foundChecksumCount = 0; - // Spin up a task to go search for all the requested checksums, adding results to the channel. var findAssetsTask = FindAllAssetsAsync(assetPath, checksums, scope, channel, cancellationToken); // Spin up a task to read from the channel and write out the assets to the pipe-writer. - var writeAssetsTask = WriteAllAssetsToPipeAsync(); + var writeAssetsTask = WriteAllAssetsToPipeAsync( + pipeWriter, channel, checksums.Length, scope, serializer, cancellationToken); // Wait for both the searching and writing tasks to finish. await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - - // If we weren't canceled, we better have found and written out all the expected assets. - Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); return; @@ -150,26 +144,48 @@ await scope.FindAssetsAsync( } } - async Task WriteAllAssetsToPipeAsync() + static async Task WriteAllAssetsToPipeAsync( + PipeWriter pipeWriter, + ChecksumChannel channel, + int checksumCount, + SolutionAssetStorage.Scope scope, + ISerializerService serializer, + CancellationToken cancellationToken) { await Task.Yield(); + // Keep track of how many checksums we found. We must find all the checksums we were asked to find. + var foundChecksumCount = 0; + while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { while (channel.Reader.TryRead(out var item)) - await WriteSingleAssetToPipeAsync(item.checksum, item.asset, cancellationToken).ConfigureAwait(false); + { + await WriteSingleAssetToPipeAsync( + pipeWriter, item.checksum, item.asset, scope, serializer, 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 != checksumCount); } - async ValueTask WriteSingleAssetToPipeAsync(Checksum checksum, object asset, CancellationToken cancellationToken) + static async ValueTask WriteSingleAssetToPipeAsync( + PipeWriter pipeWriter, + Checksum checksum, + object asset, + SolutionAssetStorage.Scope scope, + ISerializerService serializer, + CancellationToken cancellationToken) { Contract.ThrowIfNull(asset); - foundChecksumCount++; - // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect // problems with our writing. - WriteSentinelByte(); + WriteSentinelByte(pipeWriter); // 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. @@ -177,10 +193,10 @@ async ValueTask WriteSingleAssetToPipeAsync(Checksum checksum, object asset, Can using (var _ = GetTempStream(out var tempStream)) { - WriteAssetToTempStream(tempStream, checksum, asset); + WriteAssetToTempStream(pipeWriter, tempStream, checksum, asset, scope, serializer, cancellationToken); // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLength(tempStream.Length); + WriteLength(pipeWriter, tempStream.Length); // Ensure we flush out the length so the reading side can immediately read the header to determine qhow // much data to it will need to prebuffer. @@ -198,7 +214,14 @@ async ValueTask WriteSingleAssetToPipeAsync(Checksum checksum, object asset, Can await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); } - void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) + static void WriteAssetToTempStream( + PipeWriter pipeWriter, + Stream tempStream, + Checksum checksum, + object asset, + SolutionAssetStorage.Scope scope, + ISerializerService serializer, + CancellationToken cancellationToken) { using var writer = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); { @@ -214,14 +237,14 @@ void WriteAssetToTempStream(Stream tempStream, Checksum checksum, object asset) } } - void WriteSentinelByte() + static void WriteSentinelByte(PipeWriter pipeWriter) { var span = pipeWriter.GetSpan(1); span[0] = MessageSentinelByte; pipeWriter.Advance(1); } - void WriteLength(long length) + static void WriteLength(PipeWriter pipeWriter, long length) { Contract.ThrowIfTrue(length > int.MaxValue); @@ -230,7 +253,7 @@ void WriteLength(long length) pipeWriter.Advance(sizeof(int)); } - PooledObject GetTempStream(out Stream stream) + static PooledObject GetTempStream(out Stream stream) { var pooledObject = s_streamPool.GetPooledObject(); var tempStream = pooledObject.Object; From e49e8a78a4417fd481b9a78511fe5bd102f6a3cd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:43:32 -0700 Subject: [PATCH 0405/1047] simplify --- .../Core/RemoteHostAssetSerialization.cs | 83 +++++++++---------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index e56593436c4ed..d90c6254f7529 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -172,56 +172,51 @@ await WriteSingleAssetToPipeAsync( // If we weren't canceled, we better have found and written out all the expected assets. Contract.ThrowIfTrue(foundChecksumCount != checksumCount); } + } - static async ValueTask WriteSingleAssetToPipeAsync( - PipeWriter pipeWriter, - Checksum checksum, - object asset, - SolutionAssetStorage.Scope scope, - ISerializerService serializer, - 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. - WriteSentinelByte(pipeWriter); + private static async ValueTask WriteSingleAssetToPipeAsync( + PipeWriter pipeWriter, + Checksum checksum, + object asset, + SolutionAssetStorage.Scope scope, + ISerializerService serializer, + CancellationToken cancellationToken) + { + Contract.ThrowIfNull(asset); - // 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. + // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect + // problems with our writing. + WriteSentinelByte(); - using (var _ = GetTempStream(out var tempStream)) - { - WriteAssetToTempStream(pipeWriter, tempStream, checksum, asset, scope, serializer, cancellationToken); + // 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. - // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLength(pipeWriter, tempStream.Length); - - // Ensure we flush out the length so the reading side can immediately read the header to determine qhow - // much data to it will need to prebuffer. - await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + using (var _ = GetTempStream(out var tempStream)) + { + WriteAssetToTempStream(tempStream); - // Now, asynchronously copy the temp buffer over to the writer stream. - tempStream.Position = 0; - await tempStream.CopyToAsync(pipeWriter, cancellationToken).ConfigureAwait(false); - } + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteLength(tempStream.Length); - // 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. + // Ensure we flush out the length so the reading side can immediately read the header to determine qhow + // 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); } - static void WriteAssetToTempStream( - PipeWriter pipeWriter, - Stream tempStream, - Checksum checksum, - object asset, - SolutionAssetStorage.Scope scope, - ISerializerService serializer, - CancellationToken cancellationToken) + // 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); + + return; + + void WriteAssetToTempStream(Stream tempStream) { using var writer = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); { @@ -237,14 +232,14 @@ static void WriteAssetToTempStream( } } - static void WriteSentinelByte(PipeWriter pipeWriter) + void WriteSentinelByte() { var span = pipeWriter.GetSpan(1); span[0] = MessageSentinelByte; pipeWriter.Advance(1); } - static void WriteLength(PipeWriter pipeWriter, long length) + void WriteLength(long length) { Contract.ThrowIfTrue(length > int.MaxValue); @@ -253,7 +248,7 @@ static void WriteLength(PipeWriter pipeWriter, long length) pipeWriter.Advance(sizeof(int)); } - static PooledObject GetTempStream(out Stream stream) + PooledObject GetTempStream(out Stream stream) { var pooledObject = s_streamPool.GetPooledObject(); var tempStream = pooledObject.Object; From 6cb8bb4d653af00c4ca5b93f9e76f627d47a9d58 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:48:37 -0700 Subject: [PATCH 0406/1047] doc --- .../Core/Utilities/SerializableBytes.cs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs index 393a109c6815a..67f0493d4e1d4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs @@ -320,30 +320,26 @@ 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. + /// public void SetLength(long value, bool truncate) { EnsureCapacity(value); - if (value < length) + if (value < length && truncate) { - if (truncate) - { - var chunkIndex = GetChunkIndex(value); - var chunkOffset = GetChunkOffset(value); + var chunkIndex = GetChunkIndex(value); + var chunkOffset = GetChunkOffset(value); - Array.Clear(chunks[chunkIndex], chunkOffset, chunks[chunkIndex].Length - chunkOffset); + Array.Clear(chunks[chunkIndex], chunkOffset, chunks[chunkIndex].Length - chunkOffset); - var trimIndex = chunkIndex + 1; - for (var i = trimIndex; i < chunks.Count; i++) - SharedPools.ByteArray.Free(chunks[i]); + var trimIndex = chunkIndex + 1; + for (var i = trimIndex; i < chunks.Count; i++) + SharedPools.ByteArray.Free(chunks[i]); - chunks.RemoveRange(trimIndex, chunks.Count - trimIndex); - } - else - { - // TODO: do we want to clear out the remaining arrays? Or is it fine to keep the existing data in - // it just as garbage? - } + chunks.RemoveRange(trimIndex, chunks.Count - trimIndex); } length = value; From f5dbd02bd1e5fa403434763c83203099ae108c3f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:53:05 -0700 Subject: [PATCH 0407/1047] more idiomatic pattern --- .../Remote/Core/RemoteHostAssetSerialization.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index d90c6254f7529..5eada8f39d25c 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -122,6 +122,7 @@ static async Task FindAllAssetsAsync( { await Task.Yield(); + Exception? exception = null; try { await scope.FindAssetsAsync( @@ -130,17 +131,13 @@ await scope.FindAssetsAsync( (checksum, asset) => channel.Writer.TryWrite((checksum, asset)), cancellationToken).ConfigureAwait(false); } - catch (Exception ex) + catch (Exception ex) when ((exception = ex) == null) { - // If Something went wrong ensure that we complete the channel so that the writing task will stop. - // Also bubble the exception out so that the outer Task.WhenAll will bubble it up. - channel.Writer.TryComplete(ex); - throw; + throw ExceptionUtilities.Unreachable(); } finally { - // We finished searching for all the checksums, let the writer know. - channel.Writer.TryComplete(); + channel.Writer.TryComplete(exception); } } From de82278cb6914ad532229b192a1b29b6d048c544 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:55:05 -0700 Subject: [PATCH 0408/1047] docs --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 5eada8f39d25c..6d54d99cf574f 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -137,6 +137,8 @@ await scope.FindAssetsAsync( } 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); } } From 83db1be16d54b5076a86f96ffa359fb6639e8c78 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 10:59:07 -0700 Subject: [PATCH 0409/1047] docs --- .../Compiler/Core/Utilities/SerializableBytes.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs index 67f0493d4e1d4..7c8b5ec36804c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs @@ -323,6 +323,8 @@ public override void SetLength(long value) /// /// 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) { From 768128265ddcc010b83c2412c17c6bfcc85ec0b6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 11:37:07 -0700 Subject: [PATCH 0410/1047] Pass along generic args to allow for non-allocating lambda lookup paths --- .../Workspace/Solution/ChecksumCollection.cs | 16 +++-- .../Workspace/Solution/StateChecksums.cs | 68 ++++++++++--------- .../Remote/Core/SolutionAssetStorage.Scope.cs | 11 +-- .../Remote/ServiceHub/Host/TestUtils.cs | 18 ++--- 4 files changed, 60 insertions(+), 53 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 41fcd81127cef..0779fd4d20e41 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -54,11 +54,12 @@ 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, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) where TState : TextDocumentState { var hintDocument = assetPath.DocumentId; @@ -68,7 +69,7 @@ internal static async Task FindAsync( if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } else @@ -81,16 +82,17 @@ internal static async Task FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, 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, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) where T : class { Contract.ThrowIfFalse(values.Count == checksums.Children.Length); @@ -103,7 +105,7 @@ internal static void Find( var checksum = checksums.Children[i]; if (searchingChecksumsLeft.Remove(checksum)) - onAssetFound(checksum, values[i]); + onAssetFound(checksum, values[i], arg); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2a532f588897c..dd1c5d79ec793 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -113,12 +113,13 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) return result; } - public async Task FindAsync( + public async Task FindAsync( SolutionCompilationState compilationState, ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -128,10 +129,10 @@ public async Task FindAsync( if (assetPath.IncludeSolutionCompilationState) { if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) - onAssetFound(this.Checksum, this); + onAssetFound(this.Checksum, this, arg); if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) - onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap); + onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap, arg); if (compilationState.FrozenSourceGeneratedDocumentStates != null) { @@ -143,7 +144,7 @@ public async Task FindAsync( { await ChecksumCollection.FindAsync( new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), - compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + 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 @@ -161,7 +162,7 @@ await ChecksumCollection.FindAsync( if (searchingChecksumsLeft.Remove(identityChecksum)) { Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); - onAssetFound(identityChecksum, state.Identity); + onAssetFound(identityChecksum, state.Identity, arg); } } } @@ -175,7 +176,7 @@ await ChecksumCollection.FindAsync( { var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - onAssetFound(identityChecksum, state.Identity); + onAssetFound(identityChecksum, state.Identity, arg); } } } @@ -189,13 +190,13 @@ await ChecksumCollection.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, assetPath, searchingChecksumsLeft, onAssetFound, 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, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } } @@ -264,12 +265,13 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) return result; } - public async Task FindAsync( + public async Task FindAsync( SolutionState solution, ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -279,13 +281,13 @@ public async Task FindAsync( if (assetPath.IncludeSolutionState) { if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - onAssetFound(Checksum, this); + onAssetFound(Checksum, this, arg); if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) - onAssetFound(Attributes, solution.SolutionAttributes); + onAssetFound(Attributes, solution.SolutionAttributes, arg); if (assetPath.IncludeSolutionAnalyzerReferences) - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); } if (searchingChecksumsLeft.Count == 0) @@ -304,7 +306,7 @@ public async Task FindAsync( if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) { - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } else @@ -327,7 +329,7 @@ public async Task FindAsync( if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) continue; - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } } @@ -431,11 +433,12 @@ public static ProjectStateChecksums Deserialize(ObjectReader reader) return result; } - public async Task FindAsync( + public async Task FindAsync( ProjectState state, AssetPath assetPath, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -449,38 +452,38 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - onAssetFound(Checksum, this); + onAssetFound(Checksum, this, arg); if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) - onAssetFound(Info, state.ProjectInfo.Attributes); + onAssetFound(Info, state.ProjectInfo.Attributes, arg); 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); + onAssetFound(CompilationOptions, compilationOptions, arg); } 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); + onAssetFound(ParseOptions, parseOptions, arg); } if (assetPath.IncludeProjectProjectReferences) - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); if (assetPath.IncludeProjectMetadataReferences) - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); if (assetPath.IncludeProjectAnalyzerReferences) - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); } if (assetPath.IncludeDocuments) { - await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + 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); } } } @@ -502,11 +505,12 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.Text); } - public async Task FindAsync( + public async Task FindAsync( AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum); @@ -514,12 +518,12 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) - onAssetFound(Info, state.Attributes); + onAssetFound(Info, state.Attributes, arg); if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) { var text = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); - onAssetFound(Text, text); + onAssetFound(Text, text, arg); } } } diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 376028765c57a..9fe82b4126bd6 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -60,15 +60,16 @@ public async Task AddAssetsAsync( await FindAssetsAsync( assetPath, checksumsToFind, - (checksum, asset) => assetMap[checksum] = asset, + static (checksum, asset, assetMap) => assetMap[checksum] = asset, + assetMap, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); Contract.ThrowIfTrue(assetMap.Count != numberOfChecksumsToSearch); } - private async Task FindAssetsAsync( - AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, CancellationToken cancellationToken) + private async Task FindAssetsAsync( + AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, TArg arg, CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -77,13 +78,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, assetPath, remainingChecksumsToFind, onAssetFound, 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, assetPath, remainingChecksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 4363e746ad586..27047d2158e5b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -184,22 +184,22 @@ public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { - var callback = (Checksum checksum, object asset) => { map[checksum] = asset; }; + 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, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(documentChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), callback, 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); @@ -207,11 +207,11 @@ public static async Task AppendAssetMapAsync( else { var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), callback, 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, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), callback, 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); @@ -227,15 +227,15 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary { map[checksum] = asset; }; + var callback = static (Checksum checksum, object asset, Dictionary map) => { map[checksum] = asset; }; var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), callback, 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(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, cancellationToken).ConfigureAwait(false); + await documentChecksums.FindAsync(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, map, cancellationToken).ConfigureAwait(false); } } From a97700607ac4c59e068bc1cf9a0d71cd10d0df7d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:09:41 -0700 Subject: [PATCH 0411/1047] Pass along generic args to allow for non-allocating lambda lookup paths --- .../Workspace/Solution/ChecksumCollection.cs | 16 +++-- .../Workspace/Solution/StateChecksums.cs | 68 ++++++++++--------- .../Core/RemoteHostAssetSerialization.cs | 3 +- .../Remote/Core/SolutionAssetStorage.Scope.cs | 19 +++--- .../Remote/ServiceHub/Host/TestUtils.cs | 18 ++--- 5 files changed, 66 insertions(+), 58 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs index 41fcd81127cef..0779fd4d20e41 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ChecksumCollection.cs @@ -54,11 +54,12 @@ 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, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) where TState : TextDocumentState { var hintDocument = assetPath.DocumentId; @@ -68,7 +69,7 @@ internal static async Task FindAsync( if (state != null) { Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } else @@ -81,16 +82,17 @@ internal static async Task FindAsync( Contract.ThrowIfFalse(state.TryGetStateChecksums(out var stateChecksums)); - await stateChecksums.FindAsync(assetPath, state, searchingChecksumsLeft, onAssetFound, 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, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) where T : class { Contract.ThrowIfFalse(values.Count == checksums.Children.Length); @@ -103,7 +105,7 @@ internal static void Find( var checksum = checksums.Children[i]; if (searchingChecksumsLeft.Remove(checksum)) - onAssetFound(checksum, values[i]); + onAssetFound(checksum, values[i], arg); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 2a532f588897c..dd1c5d79ec793 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -113,12 +113,13 @@ public static SolutionCompilationStateChecksums Deserialize(ObjectReader reader) return result; } - public async Task FindAsync( + public async Task FindAsync( SolutionCompilationState compilationState, ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -128,10 +129,10 @@ public async Task FindAsync( if (assetPath.IncludeSolutionCompilationState) { if (assetPath.IncludeSolutionCompilationStateChecksums && searchingChecksumsLeft.Remove(this.Checksum)) - onAssetFound(this.Checksum, this); + onAssetFound(this.Checksum, this, arg); if (assetPath.IncludeSolutionSourceGeneratorExecutionVersionMap && searchingChecksumsLeft.Remove(this.SourceGeneratorExecutionVersionMap)) - onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap); + onAssetFound(this.SourceGeneratorExecutionVersionMap, compilationState.SourceGeneratorExecutionVersionMap, arg); if (compilationState.FrozenSourceGeneratedDocumentStates != null) { @@ -143,7 +144,7 @@ public async Task FindAsync( { await ChecksumCollection.FindAsync( new AssetPath(AssetPathKind.DocumentText, assetPath.ProjectId, assetPath.DocumentId), - compilationState.FrozenSourceGeneratedDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + 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 @@ -161,7 +162,7 @@ await ChecksumCollection.FindAsync( if (searchingChecksumsLeft.Remove(identityChecksum)) { Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(documentId, out var state)); - onAssetFound(identityChecksum, state.Identity); + onAssetFound(identityChecksum, state.Identity, arg); } } } @@ -175,7 +176,7 @@ await ChecksumCollection.FindAsync( { var id = FrozenSourceGeneratedDocuments.Value.Ids[i]; Contract.ThrowIfFalse(compilationState.FrozenSourceGeneratedDocumentStates.TryGetState(id, out var state)); - onAssetFound(identityChecksum, state.Identity); + onAssetFound(identityChecksum, state.Identity, arg); } } } @@ -189,13 +190,13 @@ await ChecksumCollection.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, assetPath, searchingChecksumsLeft, onAssetFound, 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, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solutionState, projectCone, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } } @@ -264,12 +265,13 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) return result; } - public async Task FindAsync( + public async Task FindAsync( SolutionState solution, ProjectCone? projectCone, AssetPath assetPath, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -279,13 +281,13 @@ public async Task FindAsync( if (assetPath.IncludeSolutionState) { if (assetPath.IncludeSolutionStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - onAssetFound(Checksum, this); + onAssetFound(Checksum, this, arg); if (assetPath.IncludeSolutionAttributes && searchingChecksumsLeft.Remove(Attributes)) - onAssetFound(Attributes, solution.SolutionAttributes); + onAssetFound(Attributes, solution.SolutionAttributes, arg); if (assetPath.IncludeSolutionAnalyzerReferences) - ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); } if (searchingChecksumsLeft.Count == 0) @@ -304,7 +306,7 @@ public async Task FindAsync( if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) { - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } else @@ -327,7 +329,7 @@ public async Task FindAsync( if (!projectState.TryGetStateChecksums(out var projectStateChecksums)) continue; - await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + await projectStateChecksums.FindAsync(projectState, assetPath, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } } @@ -431,11 +433,12 @@ public static ProjectStateChecksums Deserialize(ObjectReader reader) return result; } - public async Task FindAsync( + public async Task FindAsync( ProjectState state, AssetPath assetPath, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -449,38 +452,38 @@ public async Task FindAsync( if (assetPath.IncludeProjects) { if (assetPath.IncludeProjectStateChecksums && searchingChecksumsLeft.Remove(Checksum)) - onAssetFound(Checksum, this); + onAssetFound(Checksum, this, arg); if (assetPath.IncludeProjectAttributes && searchingChecksumsLeft.Remove(Info)) - onAssetFound(Info, state.ProjectInfo.Attributes); + onAssetFound(Info, state.ProjectInfo.Attributes, arg); 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); + onAssetFound(CompilationOptions, compilationOptions, arg); } 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); + onAssetFound(ParseOptions, parseOptions, arg); } if (assetPath.IncludeProjectProjectReferences) - ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(state.ProjectReferences, ProjectReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); if (assetPath.IncludeProjectMetadataReferences) - ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(state.MetadataReferences, MetadataReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); if (assetPath.IncludeProjectAnalyzerReferences) - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, cancellationToken); + ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, onAssetFound, arg, cancellationToken); } if (assetPath.IncludeDocuments) { - await ChecksumCollection.FindAsync(assetPath, state.DocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AdditionalDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); - await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, cancellationToken).ConfigureAwait(false); + 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); } } } @@ -502,11 +505,12 @@ public void AddAllTo(HashSet checksums) checksums.AddIfNotNullChecksum(this.Text); } - public async Task FindAsync( + public async Task FindAsync( AssetPath assetPath, TextDocumentState state, HashSet searchingChecksumsLeft, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { Debug.Assert(state.TryGetStateChecksums(out var stateChecksum) && this == stateChecksum); @@ -514,12 +518,12 @@ public async Task FindAsync( cancellationToken.ThrowIfCancellationRequested(); if (assetPath.IncludeDocumentAttributes && searchingChecksumsLeft.Remove(Info)) - onAssetFound(Info, state.Attributes); + onAssetFound(Info, state.Attributes, arg); if (assetPath.IncludeDocumentText && searchingChecksumsLeft.Remove(Text)) { var text = await SerializableSourceText.FromTextDocumentStateAsync(state, cancellationToken).ConfigureAwait(false); - onAssetFound(Text, text); + onAssetFound(Text, text, arg); } } } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 6d54d99cf574f..303cf2239fe58 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -128,7 +128,8 @@ static async Task FindAllAssetsAsync( await scope.FindAssetsAsync( assetPath, checksums, - (checksum, asset) => channel.Writer.TryWrite((checksum, asset)), + static (checksum, asset, channel) => channel.Writer.TryWrite((checksum, asset)), + channel, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index b558446686f57..acc4b58edc971 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -43,10 +43,11 @@ public void Dispose() /// Retrieve assets of specified available within from /// the storage. /// - public async Task FindAssetsAsync( + public async Task FindAssetsAsync( AssetPath assetPath, ReadOnlyMemory checksums, - Action onAssetFound, + Action onAssetFound, + TArg arg, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -57,7 +58,7 @@ public async Task FindAssetsAsync( var numberOfChecksumsToSearch = checksumsToFind.Count; Contract.ThrowIfTrue(checksumsToFind.Contains(Checksum.Null)); - await FindAssetsAsync(assetPath, checksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); + await FindAssetsAsync(assetPath, checksumsToFind, onAssetFound, arg, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(checksumsToFind.Count > 0); @@ -70,8 +71,8 @@ static void AddChecksums(ReadOnlyMemory checksums, HashSet c } } - private async Task FindAssetsAsync( - AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, CancellationToken cancellationToken) + private async Task FindAssetsAsync( + AssetPath assetPath, HashSet remainingChecksumsToFind, Action onAssetFound, TArg arg, CancellationToken cancellationToken) { var solutionState = this.CompilationState; @@ -80,13 +81,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, assetPath, remainingChecksumsToFind, onAssetFound, 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, assetPath, remainingChecksumsToFind, onAssetFound, cancellationToken).ConfigureAwait(false); + await stateChecksums.FindAsync(solutionState, this.ProjectCone, assetPath, remainingChecksumsToFind, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } @@ -106,13 +107,13 @@ public async ValueTask GetAssetAsync(Checksum checksum, CancellationToke var checksums = new ReadOnlyMemory([checksum]); object? asset = null; - await scope.FindAssetsAsync(AssetPath.FullLookupForTesting, checksums, (foundChecksum, foundAsset) => + 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; - }, cancellationToken).ConfigureAwait(false); + }, default(VoidResult), cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(asset); diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 4363e746ad586..27047d2158e5b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -184,22 +184,22 @@ public static Task AppendAssetMapAsync(this Solution solution, Dictionary map, ProjectId? projectId, CancellationToken cancellationToken) { - var callback = (Checksum checksum, object asset) => { map[checksum] = asset; }; + 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, AssetPath.FullLookupForTesting, Flatten(compilationChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(documentChecksums), callback, 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, AssetPath.FullLookupForTesting, Flatten(solutionChecksums), callback, 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); @@ -207,11 +207,11 @@ public static async Task AppendAssetMapAsync( else { var (compilationChecksums, projectCone) = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await compilationChecksums.FindAsync(solution.CompilationState, projectCone, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(compilationChecksums), callback, 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, AssetPath.SolutionAndProjectForTesting(projectId), Flatten(solutionChecksums), callback, 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); @@ -227,15 +227,15 @@ private static async Task AppendAssetMapAsync(this Project project, Dictionary { map[checksum] = asset; }; + var callback = static (Checksum checksum, object asset, Dictionary map) => { map[checksum] = asset; }; var projectChecksums = await project.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await projectChecksums.FindAsync(project.State, AssetPath.FullLookupForTesting, Flatten(projectChecksums), callback, 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(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, cancellationToken).ConfigureAwait(false); + await documentChecksums.FindAsync(AssetPathKind.Documents, document.State, Flatten(documentChecksums), callback, map, cancellationToken).ConfigureAwait(false); } } From 1843f49c2cd294207bf94c07b1655171bf6bf2de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:12:52 -0700 Subject: [PATCH 0412/1047] docs --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 303cf2239fe58..aa9a99f8c3296 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -128,6 +128,10 @@ static async Task FindAllAssetsAsync( await scope.FindAssetsAsync( assetPath, checksums, + // 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 FindAssetsAsync completed) or if cancellation + // happens. 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. static (checksum, asset, channel) => channel.Writer.TryWrite((checksum, asset)), channel, cancellationToken).ConfigureAwait(false); From 4017a8bfd9839182d30fa276c0783c34a0a95152 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:13:19 -0700 Subject: [PATCH 0413/1047] speeling --- .../Remote/Core/RemoteHostAssetSerialization.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index aa9a99f8c3296..08db37128a9c2 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -67,13 +67,12 @@ internal static class RemoteHostAssetSerialization private static readonly UnboundedChannelOptions s_channelOptions = new() { - // We have a single task reading the data from the channel and writing it to the pipe. This option - // allows the channel to operate in a more efficient manner knowing it won't have to sychronize data - // for multiple readers. + // We have a single task reading the data from the channel and writing it to the pipe. This option allows the + // channel to operate in a more efficient manner knowing it won't have to synchronize data for multiple readers. SingleReader = true, - // Currently we only have a single writer writing to the channel when we call FindAllAssetsAsync. - // However, we could change this in the future to allow the search to happen in parallel. + // Currently we only have a single writer writing to the channel when we call FindAllAssetsAsync. However, we + // could change this in the future to allow the search to happen in parallel. SingleWriter = true, }; From 77916666e2e884388664ef19f64d69dae557e4c7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:13:41 -0700 Subject: [PATCH 0414/1047] speeling --- .../Remote/Core/RemoteHostAssetSerialization.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 08db37128a9c2..84da1502c153b 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -202,8 +202,8 @@ private static async ValueTask WriteSingleAssetToPipeAsync( // Write the length of the asset to the pipe writer so the reader knows how much data to read. WriteLength(tempStream.Length); - // Ensure we flush out the length so the reading side can immediately read the header to determine qhow - // much data to it will need to prebuffer. + // 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. @@ -211,10 +211,10 @@ private static async ValueTask WriteSingleAssetToPipeAsync( 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. + // 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); return; From c5bbaf23cdaeb193a02e5d5d38e19c65185febb7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:14:26 -0700 Subject: [PATCH 0415/1047] renames --- .../Remote/Core/RemoteHostAssetSerialization.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 84da1502c153b..5c6c9eef3c3e3 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -101,10 +101,10 @@ public static async ValueTask WriteDataAsync( #endif // Spin up a task to go search for all the requested checksums, adding results to the channel. - var findAssetsTask = FindAllAssetsAsync(assetPath, checksums, scope, channel, cancellationToken); + var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync(assetPath, checksums, scope, channel, cancellationToken); // Spin up a task to read from the channel and write out the assets to the pipe-writer. - var writeAssetsTask = WriteAllAssetsToPipeAsync( + var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync( pipeWriter, channel, checksums.Length, scope, serializer, cancellationToken); // Wait for both the searching and writing tasks to finish. @@ -112,7 +112,7 @@ public static async ValueTask WriteDataAsync( return; - static async Task FindAllAssetsAsync( + static async Task FindAssetsFromScopeAndWriteToChannelAsync( AssetPath assetPath, ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, @@ -147,7 +147,7 @@ await scope.FindAssetsAsync( } } - static async Task WriteAllAssetsToPipeAsync( + static async Task ReadAssetsFromChannelAndWriteToPipeAsync( PipeWriter pipeWriter, ChecksumChannel channel, int checksumCount, From 26db24bf0bb32f9b8969cbf7bc396716648b1a38 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:17:17 -0700 Subject: [PATCH 0416/1047] pass writer and reader --- .../Core/RemoteHostAssetSerialization.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 5c6c9eef3c3e3..e9ea5c6b180d9 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis.Remote; using ChecksumChannel = Channel<(Checksum checksum, object asset)>; +using ChecksumChannelReader = ChannelReader<(Checksum checksum, object asset)>; +using ChecksumChannelWriter = ChannelWriter<(Checksum checksum, object asset)>; /// /// Contains the utilities for writing assets from the host to a pipe-writer and for reading those assets on the @@ -101,11 +103,12 @@ public static async ValueTask WriteDataAsync( #endif // Spin up a task to go search for all the requested checksums, adding results to the channel. - var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync(assetPath, checksums, scope, channel, cancellationToken); + var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync( + assetPath, checksums, scope, channel.Writer, cancellationToken); // Spin up a task to read from the channel and write out the assets to the pipe-writer. var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync( - pipeWriter, channel, checksums.Length, scope, serializer, cancellationToken); + pipeWriter, channel.Reader, checksums.Length, scope, serializer, cancellationToken); // Wait for both the searching and writing tasks to finish. await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); @@ -116,7 +119,7 @@ static async Task FindAssetsFromScopeAndWriteToChannelAsync( AssetPath assetPath, ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, - ChecksumChannel channel, + ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) { await Task.Yield(); @@ -131,8 +134,8 @@ await scope.FindAssetsAsync( // channel is only ever completed by us (after FindAssetsAsync completed) or if cancellation // happens. 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. - static (checksum, asset, channel) => channel.Writer.TryWrite((checksum, asset)), - channel, + static (checksum, asset, channelWriter) => channelWriter.TryWrite((checksum, asset)), + channelWriter, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) @@ -143,13 +146,13 @@ await scope.FindAssetsAsync( { // 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); + channelWriter.TryComplete(exception); } } static async Task ReadAssetsFromChannelAndWriteToPipeAsync( PipeWriter pipeWriter, - ChecksumChannel channel, + ChecksumChannelReader channelReader, int checksumCount, SolutionAssetStorage.Scope scope, ISerializerService serializer, @@ -160,9 +163,9 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( // Keep track of how many checksums we found. We must find all the checksums we were asked to find. var foundChecksumCount = 0; - while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - while (channel.Reader.TryRead(out var item)) + while (channelReader.TryRead(out var item)) { await WriteSingleAssetToPipeAsync( pipeWriter, item.checksum, item.asset, scope, serializer, cancellationToken).ConfigureAwait(false); From 92c292609a13e593716e324596a2799e1601e8cd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 15:31:32 -0700 Subject: [PATCH 0417/1047] Reuse reader --- .../Core/RemoteHostAssetSerialization.cs | 14 ++++++--- .../ObjectReader.ReaderReferenceMap.cs | 3 ++ .../Core/Serialization/ObjectReader.cs | 31 +++++++++++++------ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index e9ea5c6b180d9..10339e9ec7865 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -287,6 +287,11 @@ static async ValueTask ReadDataSuppressedFlowAsync( { using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); + // Get an object reader over the stream. Note; we do not check the version bytes here, as the stream is + // currently pointing at header data prior to the object data. Instead, after reading the header data, we + // will 'Reset' the reader to ensure it checks the version bytes and clears its internal state. + using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, checkVersionBytes: false, cancellationToken); + for (var i = 0; i < objectCount; i++) { // First, read the sentinel byte and the length of the data chunk we'll be reading. @@ -310,11 +315,12 @@ static async ValueTask ReadDataSuppressedFlowAsync( // from within ObjectReader.GetReader below. pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - // 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. - using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, cancellationToken); + // Let the object reader know we're resetting (so it will check its validation bits and clear its + // internal state. + reader.Reset(); + // 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(reader); var kind = (WellKnownSynchronizationKind)reader.ReadByte(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs index 7a1db7482c255..023f7f7038353 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs @@ -32,6 +32,9 @@ public void Dispose() s_objectListPool.Free(_values); } + public void Reset() + => _values.Clear(); + public void AddValue(string value) => _values.Add(value); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index 2eabf6a5a4797..05d919ac5794d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -100,34 +100,35 @@ private ObjectReader( /// 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) + => GetReader(stream, leaveOpen, checkVersionBytes: true, cancellationToken); + public static ObjectReader GetReader( Stream stream, bool leaveOpen, + bool checkVersionBytes, CancellationToken cancellationToken) + { + if (checkVersionBytes) + ReadVersionBytes(stream); + return new ObjectReader(stream, leaveOpen, cancellationToken); + } + + private static void ReadVersionBytes(Stream stream) { var b = stream.ReadByte(); if (b == -1) - { throw new EndOfStreamException(); - } if (b != VersionByte1) - { throw ExceptionUtilities.UnexpectedValue(b); - } b = stream.ReadByte(); if (b == -1) - { throw new EndOfStreamException(); - } if (b != VersionByte2) - { throw ExceptionUtilities.UnexpectedValue(b); - } - - return new ObjectReader(stream, leaveOpen, cancellationToken); } public void Dispose() @@ -135,6 +136,16 @@ public void Dispose() _stringReferenceMap.Dispose(); } + /// + /// Resets this ObjectReader to its initial state. This will re-read the version-bytes from the stream (throwing if + /// they are invalid), allowing the reader to then read the next object from the stream. + /// + public void Reset() + { + ReadVersionBytes(_reader.BaseStream); + _stringReferenceMap.Reset(); + } + public bool ReadBoolean() => _reader.ReadBoolean(); public byte ReadByte() => _reader.ReadByte(); // read as ushort because BinaryWriter fails on chars that are unicode surrogates From 78857f3b847f2a5c061e990fbaf2c7ae1282d107 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:21:10 -0700 Subject: [PATCH 0418/1047] single reader writer --- .../Core/RemoteHostAssetSerialization.cs | 120 +++++++++--------- .../ObjectReader.ReaderReferenceMap.cs | 3 - .../Core/Serialization/ObjectReader.cs | 27 ++-- .../Core/Serialization/ObjectWriter.cs | 6 +- 4 files changed, 80 insertions(+), 76 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 10339e9ec7865..f5c4cd29822d2 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -163,12 +163,17 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( // Keep track of how many checksums we found. We must find all the checksums we were asked to find. var foundChecksumCount = 0; + // 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 var pooledStream = s_streamPool.GetPooledObject(); + using var writer = new ObjectWriter(pooledStream.Object, leaveOpen: true, writeValidationBytes: false, cancellationToken); + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { while (channelReader.TryRead(out var item)) { await WriteSingleAssetToPipeAsync( - pipeWriter, item.checksum, item.asset, scope, serializer, cancellationToken).ConfigureAwait(false); + pipeWriter, pooledStream.Object, writer, item.checksum, item.asset, scope, serializer, cancellationToken).ConfigureAwait(false); foundChecksumCount++; } } @@ -182,6 +187,8 @@ await WriteSingleAssetToPipeAsync( private static async ValueTask WriteSingleAssetToPipeAsync( PipeWriter pipeWriter, + SerializableBytes.ReadWriteStream tempStream, + ObjectWriter writer, Checksum checksum, object asset, SolutionAssetStorage.Scope scope, @@ -192,27 +199,23 @@ private static async ValueTask WriteSingleAssetToPipeAsync( // We're about to send a message. Write out our sentinel byte to ensure the reading side can detect // problems with our writing. - WriteSentinelByte(); + 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(); - using (var _ = GetTempStream(out var tempStream)) - { - WriteAssetToTempStream(tempStream); - - // Write the length of the asset to the pipe writer so the reader knows how much data to read. - WriteLength(tempStream.Length); + // Write the length of the asset to the pipe writer so the reader knows how much data to read. + WriteTempStreamLengthToPipeWriter(); - // 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); + // 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); - } + // 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 @@ -222,52 +225,45 @@ private static async ValueTask WriteSingleAssetToPipeAsync( return; - void WriteAssetToTempStream(Stream tempStream) + void WriteAssetToTempStream() { - using var writer = new ObjectWriter(tempStream, leaveOpen: true, cancellationToken); - { - // Write the checksum for the asset we're writing out, so the other side knows what asset this is. - checksum.WriteTo(writer); + // 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 kind so the receiving end knows how to deserialize this asset. - var kind = asset.GetWellKnownSynchronizationKind(); - writer.WriteByte((byte)kind); + // Write out the object writer validation bytes. This will help us detect issues when reading if we've + // screwed something up. + writer.WriteValidationBytes(); - // Now serialize out the asset itself. - serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); - } + // Write the checksum for the asset we're writing out, so the other side knows what asset this is. + checksum.WriteTo(writer); + + // Write out the kind so the receiving end knows how to deserialize this asset. + writer.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); + + // Now serialize out the asset itself. + serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); } - void WriteSentinelByte() + void WriteSentinelByteToPipeWriter() { var span = pipeWriter.GetSpan(1); span[0] = MessageSentinelByte; pipeWriter.Advance(1); } - void WriteLength(long length) + void WriteTempStreamLengthToPipeWriter() { + var length = tempStream.Length; Contract.ThrowIfTrue(length > int.MaxValue); var span = pipeWriter.GetSpan(sizeof(int)); BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); pipeWriter.Advance(sizeof(int)); } - - PooledObject GetTempStream(out Stream stream) - { - var pooledObject = s_streamPool.GetPooledObject(); - var tempStream = pooledObject.Object; - tempStream.Position = 0; - - // 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.SetLength(0, truncate: false); - stream = tempStream; - return pooledObject; - } } public static ValueTask ReadDataAsync( @@ -287,9 +283,9 @@ static async ValueTask ReadDataSuppressedFlowAsync( { using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); - // Get an object reader over the stream. Note; we do not check the version bytes here, as the stream is - // currently pointing at header data prior to the object data. Instead, after reading the header data, we - // will 'Reset' the reader to ensure it checks the version bytes and clears its internal state. + // Get an object reader over the stream. Note: we do not check the version bytes here, as the stream is + // currently pointing at header data prior to the object data. Instead, we will check the version bytes + // prior to reading each asset out. using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, checkVersionBytes: false, cancellationToken); for (var i = 0; i < objectCount; i++) @@ -315,19 +311,8 @@ static async ValueTask ReadDataSuppressedFlowAsync( // from within ObjectReader.GetReader below. pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - // Let the object reader know we're resetting (so it will check its validation bits and clear its - // internal state. - reader.Reset(); - - // 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(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadByte(); - - // in service hub, cancellation means simply closed stream - var result = serializerService.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(result); - callback(checksum, (T)result, arg); + var (checksum, asset) = ReadSingleAssetFromObjectReader(reader, serializerService, cancellationToken); + callback(checksum, asset, arg); } } @@ -338,5 +323,22 @@ static async ValueTask ReadDataSuppressedFlowAsync( Contract.ThrowIfFalse(sequenceReader.TryReadLittleEndian(out int length)); return (sentinel, length); } + + static (Checksum checksum, T asset) ReadSingleAssetFromObjectReader( + ObjectReader reader, ISerializerService serializerService, CancellationToken cancellationToken) + { + // Let the object reader do it's own individual object checking. + reader.CheckVersionBytes(); + + // 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(reader); + var kind = (WellKnownSynchronizationKind)reader.ReadByte(); + + // in service hub, cancellation means simply closed stream + var result = serializerService.Deserialize(kind, reader, cancellationToken); + Contract.ThrowIfNull(result); + return (checksum, (T)result); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs index 023f7f7038353..7a1db7482c255 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.ReaderReferenceMap.cs @@ -32,9 +32,6 @@ public void Dispose() s_objectListPool.Free(_values); } - public void Reset() - => _values.Clear(); - public void AddValue(string value) => _values.Add(value); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index 05d919ac5794d..a0644f04295e9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -109,13 +109,16 @@ public static ObjectReader GetReader( bool checkVersionBytes, CancellationToken cancellationToken) { + var reader = new ObjectReader(stream, leaveOpen, cancellationToken); if (checkVersionBytes) - ReadVersionBytes(stream); - return new ObjectReader(stream, leaveOpen, cancellationToken); + reader.CheckVersionBytes(); + + return reader; } - private static void ReadVersionBytes(Stream stream) + public void CheckVersionBytes() { + var stream = _reader.BaseStream; var b = stream.ReadByte(); if (b == -1) throw new EndOfStreamException(); @@ -136,15 +139,15 @@ public void Dispose() _stringReferenceMap.Dispose(); } - /// - /// Resets this ObjectReader to its initial state. This will re-read the version-bytes from the stream (throwing if - /// they are invalid), allowing the reader to then read the next object from the stream. - /// - public void Reset() - { - ReadVersionBytes(_reader.BaseStream); - _stringReferenceMap.Reset(); - } + ///// + ///// Resets this ObjectReader to its initial state. This will re-read the version-bytes from the stream (throwing if + ///// they are invalid), allowing the reader to then read the next object from the stream. + ///// + //public void Reset() + //{ + // CheckVersionBytes(_reader.BaseStream); + // _stringReferenceMap.Reset(); + //} public bool ReadBoolean() => _reader.ReadBoolean(); public byte ReadByte() => _reader.ReadByte(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index 1aa1eb0bfdd2a..067fdbf3542d6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -85,6 +85,7 @@ private static class BufferPool public ObjectWriter( Stream stream, bool leaveOpen = false, + bool writeValidationBytes = true, CancellationToken cancellationToken = default) { // String serialization assumes both reader and writer to be of the same endianness. @@ -95,10 +96,11 @@ public ObjectWriter( _stringReferenceMap = new WriterReferenceMap(); _cancellationToken = cancellationToken; - WriteVersion(); + if (writeValidationBytes) + WriteValidationBytes(); } - private void WriteVersion() + public void WriteValidationBytes() { WriteByte(ObjectReader.VersionByte1); WriteByte(ObjectReader.VersionByte2); From 5511c59805adfb87537d2b9a26843cb1a0356c41 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:30:00 -0700 Subject: [PATCH 0419/1047] cleanup --- .../Remote/Core/RemoteHostAssetSerialization.cs | 1 - .../Compiler/Core/Serialization/ObjectReader.cs | 10 ---------- .../Compiler/Core/Serialization/ObjectWriter.cs | 13 ++++++++++++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index f5c4cd29822d2..c89645067b067 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -335,7 +335,6 @@ static async ValueTask ReadDataSuppressedFlowAsync( var checksum = Checksum.ReadFrom(reader); var kind = (WellKnownSynchronizationKind)reader.ReadByte(); - // in service hub, cancellation means simply closed stream var result = serializerService.Deserialize(kind, reader, cancellationToken); Contract.ThrowIfNull(result); return (checksum, (T)result); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index a0644f04295e9..10a132099e497 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -139,16 +139,6 @@ public void Dispose() _stringReferenceMap.Dispose(); } - ///// - ///// Resets this ObjectReader to its initial state. This will re-read the version-bytes from the stream (throwing if - ///// they are invalid), allowing the reader to then read the next object from the stream. - ///// - //public void Reset() - //{ - // CheckVersionBytes(_reader.BaseStream); - // _stringReferenceMap.Reset(); - //} - public bool ReadBoolean() => _reader.ReadBoolean(); public byte ReadByte() => _reader.ReadByte(); // read as ushort because BinaryWriter fails on chars that are unicode surrogates diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index 067fdbf3542d6..b68facbcf9b20 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -85,8 +85,19 @@ private static class BufferPool public ObjectWriter( Stream stream, bool leaveOpen = false, - bool writeValidationBytes = true, CancellationToken cancellationToken = default) + : this(stream, leaveOpen, writeValidationBytes: true, cancellationToken) + { + } + + /// + /// Whether or not the validation bytes should be immediately written into the + /// stream. + public ObjectWriter( + Stream stream, + bool leaveOpen, + bool writeValidationBytes, + CancellationToken cancellationToken) { // String serialization assumes both reader and writer to be of the same endianness. // It can be adjusted for BigEndian if needed. From 30b36eae4440eae8dbced3901fb7f7d8cf69ca3d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:31:01 -0700 Subject: [PATCH 0420/1047] Docs --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index c89645067b067..7c49f6994a73e 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -164,7 +164,9 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( var foundChecksumCount = 0; // 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. + // 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 writer = new ObjectWriter(pooledStream.Object, leaveOpen: true, writeValidationBytes: false, cancellationToken); From 6f8689b1cdab5ce8f3b798379e710a9273f40143 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:36:58 -0700 Subject: [PATCH 0421/1047] Naming and docs --- .../Core/RemoteHostAssetSerialization.cs | 8 +++---- .../Core/Serialization/ObjectReader.cs | 22 +++++++++++-------- .../Core/Serialization/ObjectWriter.cs | 11 +++++++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 7c49f6994a73e..989c7fb5c94fd 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -285,10 +285,10 @@ static async ValueTask ReadDataSuppressedFlowAsync( { using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); - // Get an object reader over the stream. Note: we do not check the version bytes here, as the stream is - // currently pointing at header data prior to the object data. Instead, we will check the version bytes + // 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 reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, checkVersionBytes: false, cancellationToken); + using var reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, checkValidationBytes: false, cancellationToken); for (var i = 0; i < objectCount; i++) { @@ -330,7 +330,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( ObjectReader reader, ISerializerService serializerService, CancellationToken cancellationToken) { // Let the object reader do it's own individual object checking. - reader.CheckVersionBytes(); + reader.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. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index 10a132099e497..d4258cd74f69b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -95,28 +95,32 @@ private ObjectReader( } /// - /// 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) - => GetReader(stream, leaveOpen, checkVersionBytes: true, cancellationToken); + => GetReader(stream, leaveOpen, checkValidationBytes: true, cancellationToken); + /// + /// 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 checkVersionBytes, + bool checkValidationBytes, CancellationToken cancellationToken) { var reader = new ObjectReader(stream, leaveOpen, cancellationToken); - if (checkVersionBytes) - reader.CheckVersionBytes(); + if (checkValidationBytes) + reader.CheckValidationBytes(); return reader; } - public void CheckVersionBytes() + public void CheckValidationBytes() { var stream = _reader.BaseStream; var b = stream.ReadByte(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index b68facbcf9b20..c8887484b3fc2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -90,9 +90,9 @@ public ObjectWriter( { } - /// - /// Whether or not the validation bytes should be immediately written into the - /// stream. + /// + /// Whether or not the validation bytes (see ) + /// should be immediately written into the stream. public ObjectWriter( Stream stream, bool leaveOpen, @@ -111,6 +111,11 @@ public ObjectWriter( WriteValidationBytes(); } + /// + /// 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); From 53a3631a8e1c11fddcf15ed8a70cc0f581d66564 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:40:52 -0700 Subject: [PATCH 0422/1047] simplify --- .../Compiler/Core/Serialization/ObjectReader.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index d4258cd74f69b..c0bc2136a1bb3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -122,18 +122,11 @@ public static ObjectReader GetReader( public void CheckValidationBytes() { - var stream = _reader.BaseStream; - var b = stream.ReadByte(); - if (b == -1) - throw new EndOfStreamException(); - + 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); } From 129bec5ece6d23c5fadcd7eac9d5a9cb5fcfb3c4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:51:09 -0700 Subject: [PATCH 0423/1047] Add dedicated tests --- .../CoreTest/ObjectSerializationTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/Workspaces/CoreTest/ObjectSerializationTests.cs b/src/Workspaces/CoreTest/ObjectSerializationTests.cs index cb9a3212a377c..9655ab6977eff 100644 --- a/src/Workspaces/CoreTest/ObjectSerializationTests.cs +++ b/src/Workspaces/CoreTest/ObjectSerializationTests.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -668,6 +669,51 @@ public void Encodings(Encoding original) EncodingTestHelpers.AssertEncodingsEqual(original, deserialized); } + [Fact] + public void TestMultipleAssetWritingAndReader() + { + using var stream = new MemoryStream(); + + // 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, CancellationToken.None)) + { + writer.WriteValidationBytes(); + writer.WriteString("Goo"); + writer.WriteString("Bar"); + + // 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("Bar"); + writer.WriteString("Goo"); + } + + stream.Position = 0; + + using var reader = ObjectReader.GetReader(stream, leaveOpen: true, checkValidationBytes: false, CancellationToken.None); + + Assert.Equal(1, reader.ReadByte()); + Assert.Equal(2, reader.ReadByte()); + + reader.CheckValidationBytes(); + Assert.Equal("Goo", reader.ReadString()); + Assert.Equal("Bar", reader.ReadString()); + + Assert.Equal(3, stream.ReadByte()); + Assert.Equal(4, stream.ReadByte()); + + reader.CheckValidationBytes(); + Assert.Equal("Bar", reader.ReadString()); + Assert.Equal("Goo", reader.ReadString()); + } + // keep these around for analyzing perf issues #if false [Fact] From 6c0ff48e995a380737c7f7cf953a2324595b2b6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 16:55:11 -0700 Subject: [PATCH 0424/1047] Remove unused cancellation tokens from api --- .../Shared/AbstractSyntaxIndex_Persistence.cs | 4 ++-- .../SymbolTreeInfo_Serialization.cs | 4 ++-- .../SerializerService_Reference.cs | 4 ++-- .../Fakes/SimpleAssetSource.cs | 4 ++-- .../Core/RemoteHostAssetSerialization.cs | 6 ++++-- ...teSemanticClassificationService.Caching.cs | 4 ++-- .../Core/Serialization/ObjectReader.cs | 19 ++++++------------- .../Core/Serialization/ObjectWriter.cs | 6 +----- 8 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs index 3183048b5c89f..e0a83cd9bd456 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs @@ -71,7 +71,7 @@ internal partial class AbstractSyntaxIndex 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); } @@ -161,7 +161,7 @@ private async Task SaveAsync( 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/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 1a070a690fd63..22ccb6dcc399f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -65,7 +65,7 @@ private static async Task LoadOrCreateAsync( 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); } @@ -98,7 +98,7 @@ private static async Task LoadOrCreateAsync( // 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/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 7face794e1425..f39c0d444450b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -44,7 +44,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) { @@ -143,7 +143,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); diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index 22ee69dda60ca..ab8478a29d58e 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs @@ -28,13 +28,13 @@ public ValueTask GetAssetsAsync( 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); } 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); callback(checksum, (T)asset, arg); diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 38674b010a948..7757b800dc484 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -26,7 +26,7 @@ public static async ValueTask WriteDataAsync( ReadOnlyMemory checksums, CancellationToken cancellationToken) { - using var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken); + using var writer = new ObjectWriter(stream, leaveOpen: true); // 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 @@ -41,6 +41,7 @@ public static async ValueTask WriteDataAsync( foreach (var (checksum, asset) in assetMap) { + cancellationToken.ThrowIfCancellationRequested(); Contract.ThrowIfNull(asset); var kind = asset.GetWellKnownSynchronizationKind(); @@ -79,7 +80,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( public static void ReadData( Stream stream, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { - using var reader = ObjectReader.GetReader(stream, leaveOpen: true, cancellationToken); + using var reader = ObjectReader.GetReader(stream, leaveOpen: true); // Ensure that no invariants were broken and that both sides of the communication channel are talking about // the same pinned solution. @@ -88,6 +89,7 @@ public static void ReadData( for (int i = 0, n = objectCount; i < n; i++) { + cancellationToken.ThrowIfCancellationRequested(); var checksum = Checksum.ReadFrom(reader); var kind = (WellKnownSynchronizationKind)reader.ReadByte(); diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs index f8fca15aa0a1b..5db02e1df8e57 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.Caching.cs @@ -156,7 +156,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); } @@ -286,7 +286,7 @@ private async Task> TryReadCachedSemanticClassifi 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; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index 2eabf6a5a4797..a79ab1f7ff82f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -28,7 +28,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 +39,9 @@ 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) + 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 +49,6 @@ private ObjectReader( _reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen); _stringReferenceMap = ReaderReferenceMap.Create(); - - _cancellationToken = cancellationToken; } /// @@ -63,8 +58,7 @@ private ObjectReader( /// public static ObjectReader? TryGetReader( Stream? stream, - bool leaveOpen = false, - CancellationToken cancellationToken = default) + bool leaveOpen = false) { if (stream == null) { @@ -91,19 +85,18 @@ private ObjectReader( #endif } - return new ObjectReader(stream, leaveOpen, cancellationToken); + return new ObjectReader(stream, leaveOpen); } /// /// Creates an from the provided . - /// Unlike , it requires the version + /// 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) + bool leaveOpen) { var b = stream.ReadByte(); if (b == -1) @@ -127,7 +120,7 @@ public static ObjectReader GetReader( throw ExceptionUtilities.UnexpectedValue(b); } - return new ObjectReader(stream, leaveOpen, cancellationToken); + return new ObjectReader(stream, leaveOpen); } 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..a56fac7bcea65 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -53,7 +53,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 +80,9 @@ 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) + bool leaveOpen = false) { // String serialization assumes both reader and writer to be of the same endianness. // It can be adjusted for BigEndian if needed. @@ -93,7 +90,6 @@ public ObjectWriter( _writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen); _stringReferenceMap = new WriterReferenceMap(); - _cancellationToken = cancellationToken; WriteVersion(); } From 0c13f52183ed7436b761a568404e7ad58c4afa6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:06:26 -0700 Subject: [PATCH 0425/1047] Simplify --- .../Compiler/Core/Serialization/ObjectReader.cs | 13 +++---------- .../Compiler/Core/Serialization/ObjectWriter.cs | 5 +---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index a79ab1f7ff82f..7b9d4773fc036 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; @@ -39,9 +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) + 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. @@ -56,9 +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) + public static ObjectReader? TryGetReader(Stream? stream, bool leaveOpen = false) { if (stream == null) { @@ -94,9 +89,7 @@ private ObjectReader( /// 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) + public static ObjectReader GetReader(Stream stream, bool leaveOpen) { var b = stream.ReadByte(); if (b == -1) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index a56fac7bcea65..0b82c5884b2ea 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; @@ -80,9 +79,7 @@ private static class BufferPool /// /// The stream to write to. /// True to leave the open after the is disposed. - public ObjectWriter( - Stream stream, - bool leaveOpen = false) + public ObjectWriter(Stream stream, bool leaveOpen = false) { // String serialization assumes both reader and writer to be of the same endianness. // It can be adjusted for BigEndian if needed. From b7c51cc9baf5c31fd18da8356060142e00cfd17c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:15:22 -0700 Subject: [PATCH 0426/1047] Backport --- .../CoreTest/ObjectSerializationTests.cs | 63 +++++++++++++++++++ .../Core/Serialization/ObjectReader.cs | 46 +++++++------- .../Core/Serialization/ObjectWriter.cs | 18 +++++- 3 files changed, 103 insertions(+), 24 deletions(-) diff --git a/src/Workspaces/CoreTest/ObjectSerializationTests.cs b/src/Workspaces/CoreTest/ObjectSerializationTests.cs index cb9a3212a377c..524b7b0e32650 100644 --- a/src/Workspaces/CoreTest/ObjectSerializationTests.cs +++ b/src/Workspaces/CoreTest/ObjectSerializationTests.cs @@ -668,6 +668,69 @@ public void Encodings(Encoding original) EncodingTestHelpers.AssertEncodingsEqual(original, deserialized); } + [Fact] + public void TestMultipleAssetWritingAndReader() + { + using var stream = new MemoryStream(); + + 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("Goo"); + 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("Goo"); + } + + 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("Goo", string1); + Assert.Equal("Bar", string2); + Assert.Equal(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("Bar", string5); + Assert.Equal("Goo", 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/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs index 7b9d4773fc036..bf40076c353dc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectReader.cs @@ -83,37 +83,39 @@ private ObjectReader(Stream stream, bool leaveOpen) 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) + => 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); } public void Dispose() diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index 0b82c5884b2ea..ab839f237963c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -80,6 +80,14 @@ private static class BufferPool /// The stream to write to. /// True to leave the open after the is disposed. 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. @@ -88,10 +96,16 @@ public ObjectWriter(Stream stream, bool leaveOpen = false) _writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen); _stringReferenceMap = new WriterReferenceMap(); - 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); From 0fce4b28d5171d92e800ae8e6fbaf61fd2483338 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:17:24 -0700 Subject: [PATCH 0427/1047] Indent --- .../Compiler/Core/Serialization/ObjectWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs index ab839f237963c..fd03f06cfe9f2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Serialization/ObjectWriter.cs @@ -80,7 +80,7 @@ private static class BufferPool /// The stream to write to. /// True to leave the open after the is disposed. public ObjectWriter(Stream stream, bool leaveOpen = false) - : this(stream, leaveOpen, writeValidationBytes: true) + : this(stream, leaveOpen, writeValidationBytes: true) { } From a2c20d236568d3f1a7a9bc358f9623d6f103ecd1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:20:05 -0700 Subject: [PATCH 0428/1047] Update test --- .../CoreTest/ObjectSerializationTests.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/CoreTest/ObjectSerializationTests.cs b/src/Workspaces/CoreTest/ObjectSerializationTests.cs index 524b7b0e32650..1afaa16289fda 100644 --- a/src/Workspaces/CoreTest/ObjectSerializationTests.cs +++ b/src/Workspaces/CoreTest/ObjectSerializationTests.cs @@ -673,6 +673,8 @@ 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 @@ -683,7 +685,7 @@ public void TestMultipleAssetWritingAndReader() using (var writer = new ObjectWriter(stream, leaveOpen: true, writeValidationBytes: false)) { writer.WriteValidationBytes(); - writer.WriteString("Goo"); + writer.WriteString(GooString); writer.WriteString("Bar"); writer.WriteString(largeString); @@ -695,7 +697,7 @@ public void TestMultipleAssetWritingAndReader() writer.WriteValidationBytes(); writer.WriteString(largeString); writer.WriteString("Bar"); - writer.WriteString("Goo"); + writer.WriteString(GooString); } stream.Position = 0; @@ -706,12 +708,16 @@ public void TestMultipleAssetWritingAndReader() Assert.Equal(2, reader.ReadByte()); reader.CheckValidationBytes(); + var string1 = reader.ReadString(); var string2 = reader.ReadString(); var string3 = reader.ReadString(); - Assert.Equal("Goo", string1); - Assert.Equal("Bar", string2); + 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()); @@ -721,8 +727,11 @@ public void TestMultipleAssetWritingAndReader() var string5 = reader.ReadString(); var string6 = reader.ReadString(); Assert.Equal(largeString, string4); - Assert.Equal("Bar", string5); - Assert.Equal("Goo", string6); + 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. From c7a02cd1fd68274f513ea4bc94f84dd761c293ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:20:05 -0700 Subject: [PATCH 0429/1047] Update test --- .../CoreTest/ObjectSerializationTests.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/CoreTest/ObjectSerializationTests.cs b/src/Workspaces/CoreTest/ObjectSerializationTests.cs index 524b7b0e32650..1afaa16289fda 100644 --- a/src/Workspaces/CoreTest/ObjectSerializationTests.cs +++ b/src/Workspaces/CoreTest/ObjectSerializationTests.cs @@ -673,6 +673,8 @@ 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 @@ -683,7 +685,7 @@ public void TestMultipleAssetWritingAndReader() using (var writer = new ObjectWriter(stream, leaveOpen: true, writeValidationBytes: false)) { writer.WriteValidationBytes(); - writer.WriteString("Goo"); + writer.WriteString(GooString); writer.WriteString("Bar"); writer.WriteString(largeString); @@ -695,7 +697,7 @@ public void TestMultipleAssetWritingAndReader() writer.WriteValidationBytes(); writer.WriteString(largeString); writer.WriteString("Bar"); - writer.WriteString("Goo"); + writer.WriteString(GooString); } stream.Position = 0; @@ -706,12 +708,16 @@ public void TestMultipleAssetWritingAndReader() Assert.Equal(2, reader.ReadByte()); reader.CheckValidationBytes(); + var string1 = reader.ReadString(); var string2 = reader.ReadString(); var string3 = reader.ReadString(); - Assert.Equal("Goo", string1); - Assert.Equal("Bar", string2); + 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()); @@ -721,8 +727,11 @@ public void TestMultipleAssetWritingAndReader() var string5 = reader.ReadString(); var string6 = reader.ReadString(); Assert.Equal(largeString, string4); - Assert.Equal("Bar", string5); - Assert.Equal("Goo", string6); + 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. From 3539b49e33751e89428a2166aaccfed6bff721cb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:36:24 -0700 Subject: [PATCH 0430/1047] Add solutionc checksum sync back --- .../Core/RemoteHostAssetSerialization.cs | 49 +++++++++++++++---- .../Remote/Core/SolutionAssetProvider.cs | 2 +- .../ServiceHub/Host/SolutionAssetSource.cs | 2 +- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index c013e22f77c4f..60a7629d3e586 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -80,6 +80,7 @@ internal static class RemoteHostAssetSerialization public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, AssetPath assetPath, + Checksum solutionChecksum, ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, ISerializerService serializer, @@ -107,7 +108,7 @@ public static async ValueTask WriteDataAsync( // Spin up a task to read from the channel and write out the assets to the pipe-writer. var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync( - pipeWriter, channel.Reader, checksums.Length, scope, serializer, cancellationToken); + pipeWriter, channel.Reader, solutionChecksum, checksums, scope, serializer, cancellationToken); // Wait for both the searching and writing tasks to finish. await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); @@ -152,7 +153,8 @@ await scope.FindAssetsAsync( static async Task ReadAssetsFromChannelAndWriteToPipeAsync( PipeWriter pipeWriter, ChecksumChannelReader channelReader, - int checksumCount, + Checksum solutionChecksum, + ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, ISerializerService serializer, CancellationToken cancellationToken) @@ -169,6 +171,12 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( using var pooledStream = s_streamPool.GetPooledObject(); using var writer = 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. + WriteSolutionChecksum(pipeWriter, solutionChecksum); + await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { while (channelReader.TryRead(out var item)) @@ -182,7 +190,14 @@ await WriteSingleAssetToPipeAsync( cancellationToken.ThrowIfCancellationRequested(); // If we weren't canceled, we better have found and written out all the expected assets. - Contract.ThrowIfTrue(foundChecksumCount != checksumCount); + Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); + } + + static void WriteSolutionChecksum(PipeWriter pipeWriter, Checksum solutionChecksum) + { + var span = pipeWriter.GetSpan(Checksum.HashSize); + solutionChecksum.WriteTo(span); + pipeWriter.Advance(Checksum.HashSize); } } @@ -268,7 +283,7 @@ void WriteTempStreamLengthToPipeWriter() } public static ValueTask ReadDataAsync( - PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, 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 @@ -277,10 +292,10 @@ public static ValueTask ReadDataAsync( // ⚠ 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, objectCount, serializerService, callback, arg, cancellationToken); + return ReadDataSuppressedFlowAsync(pipeReader, solutionChecksum, objectCount, serializerService, callback, arg, cancellationToken); static async ValueTask ReadDataSuppressedFlowAsync( - PipeReader pipeReader, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) { using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); @@ -289,17 +304,23 @@ static async ValueTask ReadDataSuppressedFlowAsync( // prior to reading each asset out. using var reader = 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 readChecksumResult = await pipeReader.ReadAtLeastAsync(Checksum.HashSize, cancellationToken).ConfigureAwait(false); + + var responseSolutionChecksum = ReadChecksum(readChecksumResult); + Contract.ThrowIfFalse(solutionChecksum == responseSolutionChecksum); + pipeReader.AdvanceTo(readChecksumResult.Buffer.GetPosition(Checksum.HashSize)); + for (var i = 0; i < objectCount; i++) { - // First, read the sentinel byte and the length of the data chunk we'll be reading. + // For each message, read the sentinel byte and the length of the data chunk we'll be reading. 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. + // Check that the sentinel is correct, and move the pipe reader forward to the end of the header. Contract.ThrowIfTrue(sentinelByte != MessageSentinelByte); - - // If so, move the pipe reader forward to the end of the header. pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); // Now buffer in the rest of the data we need to read. Because we're reading as much data in as @@ -317,6 +338,14 @@ static async ValueTask ReadDataSuppressedFlowAsync( } } + 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); + } + static (byte, int) ReadSentinelAndLength(ReadResult readResult) { var sequenceReader = new SequenceReader(readResult.Buffer); diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 9db350117ae51..477d773d028cd 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -73,7 +73,7 @@ private ValueTask WriteAssetsWorkerAsync( var serializer = _services.GetRequiredService(); return RemoteHostAssetSerialization.WriteDataAsync( - pipeWriter, assetPath, checksums, scope, serializer, cancellationToken); + pipeWriter, assetPath, solutionChecksum, checksums, scope, serializer, cancellationToken); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index eeb40a6e31fac..c85c67923a5ca 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -33,7 +33,7 @@ await RemoteCallback.InvokeServiceAsync( SolutionAssetProvider.ServiceDescriptor, (callback, cancellationToken) => callback.InvokeAsync( (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken), - (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, checksums.Length, serializerService, assetCallback, arg, cancellationToken), + (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg, cancellationToken), cancellationToken), cancellationToken).ConfigureAwait(false); } From e7175948cc03a10ea1067bb892c447727e31fa57 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 17:38:03 -0700 Subject: [PATCH 0431/1047] Revert --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 60a7629d3e586..f41b5e837f41a 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -312,7 +312,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( Contract.ThrowIfFalse(solutionChecksum == responseSolutionChecksum); pipeReader.AdvanceTo(readChecksumResult.Buffer.GetPosition(Checksum.HashSize)); - for (var i = 0; i < objectCount; i++) + for (int i = 0, n = objectCount; i < n; i++) { // For each message, read the sentinel byte and the length of the data chunk we'll be reading. const int HeaderSize = sizeof(byte) + sizeof(int); From b0acd71169702235cacb8960a0a52fce0d1d9c12 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 18:49:15 -0700 Subject: [PATCH 0432/1047] move in --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index f41b5e837f41a..a4659f6751067 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -122,11 +122,11 @@ static async Task FindAssetsFromScopeAndWriteToChannelAsync( ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) { - await Task.Yield(); - Exception? exception = null; try { + await Task.Yield(); + await scope.FindAssetsAsync( assetPath, checksums, From faf6254bfb508cc14bf693d88485429af9024b2a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 18:58:42 -0700 Subject: [PATCH 0433/1047] remove unneeded argument --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 6 ++---- src/Workspaces/Remote/Core/SolutionAssetProvider.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index a4659f6751067..94261e27a4a4d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -80,7 +80,6 @@ internal static class RemoteHostAssetSerialization public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, AssetPath assetPath, - Checksum solutionChecksum, ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, ISerializerService serializer, @@ -108,7 +107,7 @@ public static async ValueTask WriteDataAsync( // Spin up a task to read from the channel and write out the assets to the pipe-writer. var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync( - pipeWriter, channel.Reader, solutionChecksum, checksums, scope, serializer, cancellationToken); + pipeWriter, channel.Reader, checksums, scope, serializer, cancellationToken); // Wait for both the searching and writing tasks to finish. await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); @@ -153,7 +152,6 @@ await scope.FindAssetsAsync( static async Task ReadAssetsFromChannelAndWriteToPipeAsync( PipeWriter pipeWriter, ChecksumChannelReader channelReader, - Checksum solutionChecksum, ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, ISerializerService serializer, @@ -174,7 +172,7 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( // 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. - WriteSolutionChecksum(pipeWriter, solutionChecksum); + WriteSolutionChecksum(pipeWriter, scope.SolutionChecksum); await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 477d773d028cd..9db350117ae51 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -73,7 +73,7 @@ private ValueTask WriteAssetsWorkerAsync( var serializer = _services.GetRequiredService(); return RemoteHostAssetSerialization.WriteDataAsync( - pipeWriter, assetPath, solutionChecksum, checksums, scope, serializer, cancellationToken); + pipeWriter, assetPath, checksums, scope, serializer, cancellationToken); } } } From 4cdf8fc862ced77c1e2a55c1f9ed523ce8c0974c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:01:43 -0700 Subject: [PATCH 0434/1047] Inline --- .../Remote/Core/SolutionAssetProvider.cs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 9db350117ae51..03108649f532a 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -21,7 +21,8 @@ internal sealed class SolutionAssetProvider(SolutionServices services) : ISoluti internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); - private readonly SolutionServices _services = services; + private readonly SolutionAssetStorage _assetStorage = services.GetRequiredService().AssetStorage; + private readonly ISerializerService _serializer = services.GetRequiredService(); public ValueTask WriteAssetsAsync( PipeWriter pipeWriter, @@ -47,7 +48,9 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s Exception? exception = null; try { - await WriteAssetsWorkerAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken).ConfigureAwait(false); + var scope = _assetStorage.GetScope(solutionChecksum); + await RemoteHostAssetSerialization.WriteDataAsync( + pipeWriter, assetPath, checksums, scope, _serializer, cancellationToken).ConfigureAwait(false); ; } catch (Exception ex) when ((exception = ex) == null) { @@ -59,21 +62,5 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s } } } - - private ValueTask WriteAssetsWorkerAsync( - PipeWriter pipeWriter, - Checksum solutionChecksum, - AssetPath assetPath, - ReadOnlyMemory checksums, - CancellationToken cancellationToken) - { - var assetStorage = _services.GetRequiredService().AssetStorage; - var scope = assetStorage.GetScope(solutionChecksum); - - var serializer = _services.GetRequiredService(); - - return RemoteHostAssetSerialization.WriteDataAsync( - pipeWriter, assetPath, checksums, scope, serializer, cancellationToken); - } } } From cffa6521ad84c47dee749cf6e2da0684be6fbe1b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:03:07 -0700 Subject: [PATCH 0435/1047] Reorder --- .../Core/RemoteHostAssetSerialization.cs | 12 +-- .../Remote/Core/SolutionAssetProvider.cs | 87 +++++++++---------- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 94261e27a4a4d..4a504601dac87 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -79,9 +79,9 @@ internal static class RemoteHostAssetSerialization public static async ValueTask WriteDataAsync( PipeWriter pipeWriter, + SolutionAssetStorage.Scope scope, AssetPath assetPath, ReadOnlyMemory checksums, - SolutionAssetStorage.Scope scope, ISerializerService serializer, CancellationToken cancellationToken) { @@ -103,11 +103,11 @@ public static async ValueTask WriteDataAsync( // Spin up a task to go search for all the requested checksums, adding results to the channel. var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync( - assetPath, checksums, scope, channel.Writer, cancellationToken); + assetPath, scope, checksums, channel.Writer, cancellationToken); // Spin up a task to read from the channel and write out the assets to the pipe-writer. var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync( - pipeWriter, channel.Reader, checksums, scope, serializer, cancellationToken); + pipeWriter, scope, checksums, serializer, channel.Reader, cancellationToken); // Wait for both the searching and writing tasks to finish. await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); @@ -116,8 +116,8 @@ public static async ValueTask WriteDataAsync( static async Task FindAssetsFromScopeAndWriteToChannelAsync( AssetPath assetPath, - ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, + ReadOnlyMemory checksums, ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) { @@ -151,10 +151,10 @@ await scope.FindAssetsAsync( static async Task ReadAssetsFromChannelAndWriteToPipeAsync( PipeWriter pipeWriter, - ChecksumChannelReader channelReader, - ReadOnlyMemory checksums, SolutionAssetStorage.Scope scope, + ReadOnlyMemory checksums, ISerializerService serializer, + ChecksumChannelReader channelReader, CancellationToken cancellationToken) { await Task.Yield(); diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 03108649f532a..ab95d972f2169 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -10,56 +10,55 @@ using Microsoft.CodeAnalysis.Serialization; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Remote +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 { - /// - /// 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"; + public const string ServiceName = "SolutionAssetProvider"; - internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); + internal static ServiceDescriptor ServiceDescriptor { get; } = ServiceDescriptor.CreateInProcServiceDescriptor(ServiceDescriptors.ComponentName, ServiceName, suffix: "", ServiceDescriptors.GetFeatureDisplayName); - private readonly SolutionAssetStorage _assetStorage = services.GetRequiredService().AssetStorage; - private readonly ISerializerService _serializer = services.GetRequiredService(); + private readonly SolutionAssetStorage _assetStorage = services.GetRequiredService().AssetStorage; + private readonly ISerializerService _serializer = services.GetRequiredService(); - 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. 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); + 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. 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) + async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum solutionChecksum, AssetPath assetPath, 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 + { + var scope = _assetStorage.GetScope(solutionChecksum); + await RemoteHostAssetSerialization.WriteDataAsync( + pipeWriter, scope, assetPath, checksums, _serializer, cancellationToken).ConfigureAwait(false); ; + } + catch (Exception ex) when ((exception = ex) == null) + { + throw ExceptionUtilities.Unreachable(); + } + finally { - // 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 - { - var scope = _assetStorage.GetScope(solutionChecksum); - await RemoteHostAssetSerialization.WriteDataAsync( - pipeWriter, assetPath, checksums, scope, _serializer, cancellationToken).ConfigureAwait(false); ; - } - catch (Exception ex) when ((exception = ex) == null) - { - throw ExceptionUtilities.Unreachable(); - } - finally - { - await pipeWriter.CompleteAsync(exception).ConfigureAwait(false); - } + await pipeWriter.CompleteAsync(exception).ConfigureAwait(false); } } } From e037d92e40824f9c7009bdfc8d1af98ec9c84053 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:07:21 -0700 Subject: [PATCH 0436/1047] move --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 4a504601dac87..9e60693241833 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -159,9 +159,6 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( { await Task.Yield(); - // Keep track of how many checksums we found. We must find all the checksums we were asked to find. - var foundChecksumCount = 0; - // 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 @@ -175,6 +172,9 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( WriteSolutionChecksum(pipeWriter, scope.SolutionChecksum); 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; + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { while (channelReader.TryRead(out var item)) From 5ef1bc4c4e52cd3d2596e4de8957de768c1b7f8a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:09:48 -0700 Subject: [PATCH 0437/1047] Standard pattern --- .../Core/Portable/Workspace/Solution/Checksum.cs | 8 ++++++++ .../Remote/Core/RemoteHostAssetSerialization.cs | 9 +-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index 71fd24bad8b57..f425a52edcacb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -5,6 +5,7 @@ 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; @@ -88,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()); diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 9e60693241833..7c0a5f203dd57 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -169,7 +169,7 @@ static async Task ReadAssetsFromChannelAndWriteToPipeAsync( // 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. - WriteSolutionChecksum(pipeWriter, scope.SolutionChecksum); + 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. @@ -190,13 +190,6 @@ await WriteSingleAssetToPipeAsync( // If we weren't canceled, we better have found and written out all the expected assets. Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); } - - static void WriteSolutionChecksum(PipeWriter pipeWriter, Checksum solutionChecksum) - { - var span = pipeWriter.GetSpan(Checksum.HashSize); - solutionChecksum.WriteTo(span); - pipeWriter.Advance(Checksum.HashSize); - } } private static async ValueTask WriteSingleAssetToPipeAsync( From 1d7be1e6ebfafb64305d43735c2a640ae17ecfe9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:17:39 -0700 Subject: [PATCH 0438/1047] Extract into helper method --- .../Portable/Workspace/Solution/Checksum.cs | 3 ++ .../Core/RemoteHostAssetSerialization.cs | 32 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index f425a52edcacb..25d325d6565fa 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.IO.Pipelines; @@ -10,6 +11,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 7c0a5f203dd57..c18cdaf002170 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -297,11 +297,8 @@ static async ValueTask ReadDataSuppressedFlowAsync( // Ensure that no invariants were broken and that both sides of the communication channel are talking about // the same pinned solution. - var readChecksumResult = await pipeReader.ReadAtLeastAsync(Checksum.HashSize, cancellationToken).ConfigureAwait(false); - - var responseSolutionChecksum = ReadChecksum(readChecksumResult); + var responseSolutionChecksum = await ReadChecksumFromPipeReaderAsync(pipeReader, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(solutionChecksum == responseSolutionChecksum); - pipeReader.AdvanceTo(readChecksumResult.Buffer.GetPosition(Checksum.HashSize)); for (int i = 0, n = objectCount; i < n; i++) { @@ -329,14 +326,6 @@ static async ValueTask ReadDataSuppressedFlowAsync( } } - 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); - } - static (byte, int) ReadSentinelAndLength(ReadResult readResult) { var sequenceReader = new SequenceReader(readResult.Buffer); @@ -360,5 +349,24 @@ static Checksum ReadChecksum(ReadResult readResult) Contract.ThrowIfNull(result); return (checksum, (T)result); } + + // 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). + static async ValueTask ReadChecksumFromPipeReaderAsync(PipeReader pipeReader, 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; + + 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); + } + } } } From 6cae14424829535eedb30a797f387724a8d15834 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:22:30 -0700 Subject: [PATCH 0439/1047] Extract into helper method --- .../Core/RemoteHostAssetSerialization.cs | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index c18cdaf002170..444ac0c05a21d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -274,7 +274,7 @@ void WriteTempStreamLengthToPipeWriter() } public static ValueTask ReadDataAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializer, Action callback, TArg arg, 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 @@ -283,10 +283,10 @@ public static ValueTask ReadDataAsync( // ⚠ 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, callback, arg, cancellationToken); + return ReadDataSuppressedFlowAsync(pipeReader, solutionChecksum, objectCount, serializer, callback, arg, cancellationToken); static async ValueTask ReadDataSuppressedFlowAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializerService, Action callback, TArg arg, CancellationToken cancellationToken) + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializer, Action callback, TArg arg, CancellationToken cancellationToken) { using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); @@ -300,30 +300,43 @@ static async ValueTask ReadDataSuppressedFlowAsync( var responseSolutionChecksum = await ReadChecksumFromPipeReaderAsync(pipeReader, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(solutionChecksum == responseSolutionChecksum); + // Now actually read all the messages we expect to get. for (int i = 0, n = objectCount; i < n; i++) - { - // For each message, read the sentinel byte and the length of the data chunk we'll be reading. - 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 != MessageSentinelByte); - pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); - - // 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 - // from within ObjectReader.GetReader below. - pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - - var (checksum, asset) = ReadSingleAssetFromObjectReader(reader, serializerService, cancellationToken); - callback(checksum, asset, arg); - } + await ReadSingleMessageAsync(pipeReader, reader, serializer, callback, arg, cancellationToken).ConfigureAwait(false); + } + + static async ValueTask ReadSingleMessageAsync( + PipeReader pipeReader, ObjectReader reader, ISerializerService serializer, Action callback, TArg arg, CancellationToken cancellationToken) + { + // For each message, read the sentinel byte and the length of the data chunk we'll be reading. + var length = await CheckSentinelByteAndReadLengthAsync(pipeReader, 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 + // from within ObjectReader.GetReader below. + pipeReader.AdvanceTo(fillReadResult.Buffer.Start); + + var (checksum, asset) = ReadSingleAssetFromObjectReader(reader, serializer, cancellationToken); + callback(checksum, asset, arg); + } + + static async ValueTask CheckSentinelByteAndReadLengthAsync(PipeReader pipeReader, 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 != MessageSentinelByte); + pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); + + return length; } static (byte, int) ReadSentinelAndLength(ReadResult readResult) @@ -335,7 +348,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( } static (Checksum checksum, T asset) ReadSingleAssetFromObjectReader( - ObjectReader reader, ISerializerService serializerService, CancellationToken cancellationToken) + ObjectReader reader, ISerializerService serializer, CancellationToken cancellationToken) { // Let the object reader do it's own individual object checking. reader.CheckValidationBytes(); @@ -345,7 +358,7 @@ static async ValueTask ReadDataSuppressedFlowAsync( var checksum = Checksum.ReadFrom(reader); var kind = (WellKnownSynchronizationKind)reader.ReadByte(); - var result = serializerService.Deserialize(kind, reader, cancellationToken); + var result = serializer.Deserialize(kind, reader, cancellationToken); Contract.ThrowIfNull(result); return (checksum, (T)result); } From 245776af18ae34a93c3a0b212b40f24fc0ecbfa9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:23:36 -0700 Subject: [PATCH 0440/1047] reorder --- .../Core/RemoteHostAssetSerialization.cs | 59 ++++++++----------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 444ac0c05a21d..e468770288e54 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -321,8 +321,17 @@ static async ValueTask ReadSingleMessageAsync( // from within ObjectReader.GetReader below. pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - var (checksum, asset) = ReadSingleAssetFromObjectReader(reader, serializer, cancellationToken); - callback(checksum, asset, arg); + // Let the object reader do it's own individual object checking. + reader.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(reader); + var kind = (WellKnownSynchronizationKind)reader.ReadByte(); + + var asset = serializer.Deserialize(kind, reader, cancellationToken); + Contract.ThrowIfNull(asset); + callback(checksum, (T)asset, arg); } static async ValueTask CheckSentinelByteAndReadLengthAsync(PipeReader pipeReader, CancellationToken cancellationToken) @@ -339,30 +348,6 @@ static async ValueTask CheckSentinelByteAndReadLengthAsync(PipeReader pipeR return length; } - 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); - } - - static (Checksum checksum, T asset) ReadSingleAssetFromObjectReader( - ObjectReader reader, ISerializerService serializer, CancellationToken cancellationToken) - { - // Let the object reader do it's own individual object checking. - reader.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(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadByte(); - - var result = serializer.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(result); - return (checksum, (T)result); - } - // 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). static async ValueTask ReadChecksumFromPipeReaderAsync(PipeReader pipeReader, CancellationToken cancellationToken) @@ -372,14 +357,22 @@ static async ValueTask ReadChecksumFromPipeReaderAsync(PipeReader pipe var checksum = ReadChecksum(readChecksumResult); pipeReader.AdvanceTo(readChecksumResult.Buffer.GetPosition(Checksum.HashSize)); return checksum; + } - 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); - } + 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); + } + + 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); } } } From 8272cd3a3b935549190254faddd5e95f976fa66e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:43:19 -0700 Subject: [PATCH 0441/1047] In progress --- .../Core/RemoteHostAssetSerialization.cs | 246 +++++++++--------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index e468770288e54..a922736e2509a 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -55,7 +55,7 @@ namespace Microsoft.CodeAnalysis.Remote; /// 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 static class RemoteHostAssetSerialization +internal readonly struct RemoteHostAssetWriter : IDisposable { /// /// A sentinel byte we place between messages. Ensures we can detect when something has gone wrong as soon as @@ -77,13 +77,39 @@ internal static class RemoteHostAssetSerialization SingleWriter = true, }; - public static async ValueTask WriteDataAsync( - PipeWriter pipeWriter, - SolutionAssetStorage.Scope scope, - AssetPath assetPath, - ReadOnlyMemory checksums, - ISerializerService serializer, - CancellationToken cancellationToken) + private readonly PipeWriter _pipeWriter; + private readonly SolutionAssetStorage.Scope _scope; + private readonly AssetPath _assetPath; + private readonly ReadOnlyMemory _checksums; + private readonly ISerializerService _serializer; + + private readonly PooledObject _pooledStream; + private readonly SerializableBytes.ReadWriteStream TempStream => _pooledStream.Object; + private readonly ObjectWriter _writer; + + public RemoteHostAssetWriter(PipeWriter pipeWriter, SolutionAssetStorage.Scope scope, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializer) + { + _pipeWriter = pipeWriter; + _scope = scope; + _assetPath = assetPath; + _checksums = checksums; + _serializer = serializer; + + // 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. + _pooledStream = s_streamPool.GetPooledObject(); + _writer = new ObjectWriter(_pooledStream.Object, leaveOpen: true, writeValidationBytes: false); + } + + public void Dispose() + { + _writer.Dispose(); + _pooledStream.Dispose(); + } + + public async ValueTask WriteDataAsync(CancellationToken cancellationToken) { // Create a channel to communicate between the searching and writing tasks. This allows the searching task to // find items, add them to the channel synchronously, and immediately continue searching for more items. @@ -102,104 +128,79 @@ public static async ValueTask WriteDataAsync( #endif // Spin up a task to go search for all the requested checksums, adding results to the channel. - var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync( - assetPath, scope, checksums, channel.Writer, cancellationToken); + var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync(channel.Writer, cancellationToken); // Spin up a task to read from the channel and write out the assets to the pipe-writer. - var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync( - pipeWriter, scope, checksums, serializer, channel.Reader, cancellationToken); + var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync(channel.Reader, cancellationToken); // Wait for both the searching and writing tasks to finish. await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); + } - return; + private async Task FindAssetsFromScopeAndWriteToChannelAsync( + ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) + { + Exception? exception = null; + try + { + await Task.Yield(); - static async Task FindAssetsFromScopeAndWriteToChannelAsync( - AssetPath assetPath, - SolutionAssetStorage.Scope scope, - ReadOnlyMemory checksums, - ChecksumChannelWriter channelWriter, - CancellationToken cancellationToken) + await _scope.FindAssetsAsync( + _assetPath, + _checksums, + // 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 FindAssetsAsync completed) or if cancellation + // happens. 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. + static (checksum, asset, channelWriter) => channelWriter.TryWrite((checksum, asset)), + channelWriter, + cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when ((exception = ex) == null) { - Exception? exception = null; - try - { - await Task.Yield(); - - await scope.FindAssetsAsync( - assetPath, - checksums, - // 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 FindAssetsAsync completed) or if cancellation - // happens. 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. - static (checksum, asset, channelWriter) => channelWriter.TryWrite((checksum, asset)), - channelWriter, - cancellationToken).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. - channelWriter.TryComplete(exception); - } + throw ExceptionUtilities.Unreachable(); } - - static async Task ReadAssetsFromChannelAndWriteToPipeAsync( - PipeWriter pipeWriter, - SolutionAssetStorage.Scope scope, - ReadOnlyMemory checksums, - ISerializerService serializer, - ChecksumChannelReader channelReader, - CancellationToken cancellationToken) + finally { - await Task.Yield(); + // No matter what path we take (exceptional or non-exceptional), always complete the channel so the + // writing task knows it's done. + channelWriter.TryComplete(exception); + } + } - // 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 writer = new ObjectWriter(pooledStream.Object, leaveOpen: true, writeValidationBytes: false); + private async Task ReadAssetsFromChannelAndWriteToPipeAsync( + ChecksumChannelReader channelReader, CancellationToken cancellationToken) + { + await Task.Yield(); - // 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); + // 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; + // Keep track of how many checksums we found. We must find all the checksums we were asked to find. + var foundChecksumCount = 0; - while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (channelReader.TryRead(out var item)) { - while (channelReader.TryRead(out var item)) - { - await WriteSingleAssetToPipeAsync( - pipeWriter, pooledStream.Object, writer, item.checksum, item.asset, scope, serializer, cancellationToken).ConfigureAwait(false); - foundChecksumCount++; - } + await WriteSingleAssetToPipeAsync( + item.checksum, item.asset, cancellationToken).ConfigureAwait(false); + foundChecksumCount++; } + } - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - // If we weren't canceled, we better have found and written out all the expected assets. - Contract.ThrowIfTrue(foundChecksumCount != checksums.Length); - } + // If we weren't canceled, we better have found and written out all the expected assets. + Contract.ThrowIfTrue(foundChecksumCount != _checksums.Length); } - private static async ValueTask WriteSingleAssetToPipeAsync( - PipeWriter pipeWriter, - SerializableBytes.ReadWriteStream tempStream, - ObjectWriter writer, + private async ValueTask WriteSingleAssetToPipeAsync( Checksum checksum, object asset, - SolutionAssetStorage.Scope scope, - ISerializerService serializer, CancellationToken cancellationToken) { Contract.ThrowIfNull(asset); @@ -211,66 +212,65 @@ private static async ValueTask WriteSingleAssetToPipeAsync( // 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(); + WriteAssetToTempStream(checksum, asset, cancellationToken); // Write the length of the asset to the pipe writer so the reader knows how much data to read. WriteTempStreamLengthToPipeWriter(); // 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); + 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); + this.TempStream.Position = 0; + await this.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); - - return; + await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); + } - void WriteAssetToTempStream() - { - // 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); + private void WriteAssetToTempStream( + 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. + this.TempStream.Position = 0; + this.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. - writer.WriteValidationBytes(); + // Write out the object writer validation bytes. This will help us detect issues when reading if we've + // screwed something up. + _writer.WriteValidationBytes(); - // Write the checksum for the asset we're writing out, so the other side knows what asset this is. - checksum.WriteTo(writer); + // Write the checksum for the asset we're writing out, so the other side knows what asset this is. + checksum.WriteTo(_writer); - // Write out the kind so the receiving end knows how to deserialize this asset. - writer.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); + // Write out the kind so the receiving end knows how to deserialize this asset. + _writer.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); - // Now serialize out the asset itself. - serializer.Serialize(asset, writer, scope.ReplicationContext, cancellationToken); - } + // Now serialize out the asset itself. + _serializer.Serialize(asset, _writer, _scope.ReplicationContext, cancellationToken); + } - void WriteSentinelByteToPipeWriter() - { - var span = pipeWriter.GetSpan(1); - span[0] = MessageSentinelByte; - pipeWriter.Advance(1); - } + private void WriteSentinelByteToPipeWriter() + { + var span = _pipeWriter.GetSpan(1); + span[0] = MessageSentinelByte; + _pipeWriter.Advance(1); + } - void WriteTempStreamLengthToPipeWriter() - { - var length = tempStream.Length; - Contract.ThrowIfTrue(length > int.MaxValue); + private void WriteTempStreamLengthToPipeWriter() + { + var length = this.TempStream.Length; + Contract.ThrowIfTrue(length > int.MaxValue); - var span = pipeWriter.GetSpan(sizeof(int)); - BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); - pipeWriter.Advance(sizeof(int)); - } + var span = _pipeWriter.GetSpan(sizeof(int)); + BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); + _pipeWriter.Advance(sizeof(int)); } public static ValueTask ReadDataAsync( From db347b0df22cab173ee0cb065fd57341ba938dc8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 19:52:06 -0700 Subject: [PATCH 0442/1047] In progress --- .../Core/RemoteHostAssetSerialization.cs | 190 ++++++++++-------- .../Remote/Core/SolutionAssetProvider.cs | 4 +- .../ServiceHub/Host/SolutionAssetSource.cs | 6 +- 3 files changed, 116 insertions(+), 84 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index a922736e2509a..5137d591c5307 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Buffers.Binary; +using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Channels; @@ -62,7 +63,7 @@ namespace Microsoft.CodeAnalysis.Remote; /// possible. Note: the value we pick is neither ascii nor extended ascii. So it's very unlikely to appear /// accidentally. /// - private const byte MessageSentinelByte = 0b10010000; + public const byte MessageSentinelByte = 0b10010000; private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); @@ -272,9 +273,45 @@ private void WriteTempStreamLengthToPipeWriter() BinaryPrimitives.WriteInt32LittleEndian(span, (int)length); _pipeWriter.Advance(sizeof(int)); } +} + +internal readonly struct RemoteHostAssetReader : IDisposable +{ + private readonly PipeReader _pipeReader; + private readonly Checksum _solutionChecksum; + private readonly int _objectCount; + private readonly ISerializerService _serializer; + private readonly Action _callback; + private readonly TArg _arg; + + private readonly Stream _pipeReaderStream; + private readonly ObjectReader _reader; - public static ValueTask ReadDataAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializer, Action callback, TArg arg, CancellationToken cancellationToken) + public RemoteHostAssetReader( + PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializer, Action callback, TArg arg) + { + _pipeReader = pipeReader; + _solutionChecksum = solutionChecksum; + _objectCount = objectCount; + _serializer = serializer; + _callback = callback; + _arg = arg; + + _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. + _reader = ObjectReader.GetReader(_pipeReaderStream, leaveOpen: true, checkValidationBytes: false); + } + + public void Dispose() + { + _pipeReaderStream.Dispose(); + _reader.Dispose(); + } + + 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 @@ -283,96 +320,87 @@ public static ValueTask ReadDataAsync( // ⚠ 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, serializer, callback, arg, cancellationToken); + return ReadDataSuppressedFlowAsync(cancellationToken); + } - static async ValueTask ReadDataSuppressedFlowAsync( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializer, Action callback, TArg arg, CancellationToken cancellationToken) - { - using var pipeReaderStream = pipeReader.AsStream(leaveOpen: true); + private async ValueTask ReadDataSuppressedFlowAsync(CancellationToken cancellationToken) + { + // 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 (int i = 0, n = _objectCount; i < n; i++) + await ReadSingleMessageAsync(cancellationToken).ConfigureAwait(false); + } - // 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 reader = ObjectReader.GetReader(pipeReaderStream, leaveOpen: true, checkValidationBytes: false); + private async ValueTask ReadSingleMessageAsync(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); - // 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(pipeReader, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfFalse(solutionChecksum == responseSolutionChecksum); + // 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); - // Now actually read all the messages we expect to get. - for (int i = 0, n = objectCount; i < n; i++) - await ReadSingleMessageAsync(pipeReader, reader, serializer, callback, arg, 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 + // from within ObjectReader.GetReader below. + _pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - static async ValueTask ReadSingleMessageAsync( - PipeReader pipeReader, ObjectReader reader, ISerializerService serializer, Action callback, TArg arg, CancellationToken cancellationToken) - { - // For each message, read the sentinel byte and the length of the data chunk we'll be reading. - var length = await CheckSentinelByteAndReadLengthAsync(pipeReader, 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 - // from within ObjectReader.GetReader below. - pipeReader.AdvanceTo(fillReadResult.Buffer.Start); - - // Let the object reader do it's own individual object checking. - reader.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(reader); - var kind = (WellKnownSynchronizationKind)reader.ReadByte(); - - var asset = serializer.Deserialize(kind, reader, cancellationToken); - Contract.ThrowIfNull(asset); - callback(checksum, (T)asset, arg); - } + // Let the object reader do it's own individual object checking. + _reader.CheckValidationBytes(); - static async ValueTask CheckSentinelByteAndReadLengthAsync(PipeReader pipeReader, CancellationToken cancellationToken) - { - const int HeaderSize = sizeof(byte) + sizeof(int); + // 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(_reader); + var kind = (WellKnownSynchronizationKind)_reader.ReadByte(); - var lengthReadResult = await pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); - var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); + var asset = _serializer.Deserialize(kind, _reader, cancellationToken); + Contract.ThrowIfNull(asset); + _callback(checksum, (T)asset, _arg); + } - // Check that the sentinel is correct, and move the pipe reader forward to the end of the header. - Contract.ThrowIfTrue(sentinelByte != MessageSentinelByte); - pipeReader.AdvanceTo(lengthReadResult.Buffer.GetPosition(HeaderSize)); + private async ValueTask CheckSentinelByteAndReadLengthAsync(CancellationToken cancellationToken) + { + const int HeaderSize = sizeof(byte) + sizeof(int); - return length; - } + var lengthReadResult = await _pipeReader.ReadAtLeastAsync(HeaderSize, cancellationToken).ConfigureAwait(false); + var (sentinelByte, length) = ReadSentinelAndLength(lengthReadResult); - // 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). - static async ValueTask ReadChecksumFromPipeReaderAsync(PipeReader pipeReader, CancellationToken cancellationToken) - { - var readChecksumResult = await pipeReader.ReadAtLeastAsync(Checksum.HashSize, cancellationToken).ConfigureAwait(false); + // 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)); - var checksum = ReadChecksum(readChecksumResult); - pipeReader.AdvanceTo(readChecksumResult.Buffer.GetPosition(Checksum.HashSize)); - return checksum; - } + return length; + } - 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); - } + // 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); - 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); - } + 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/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index ab95d972f2169..36a8952a424cf 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -49,8 +49,8 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s try { var scope = _assetStorage.GetScope(solutionChecksum); - await RemoteHostAssetSerialization.WriteDataAsync( - pipeWriter, scope, assetPath, checksums, _serializer, cancellationToken).ConfigureAwait(false); ; + using var writer = new RemoteHostAssetWriter(pipeWriter, scope, assetPath, checksums, _serializer); + await writer.WriteDataAsync(cancellationToken).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index c85c67923a5ca..dc6df985fc5e3 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -33,7 +33,11 @@ await RemoteCallback.InvokeServiceAsync( SolutionAssetProvider.ServiceDescriptor, (callback, cancellationToken) => callback.InvokeAsync( (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken), - (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg, cancellationToken), + async (pipeReader, cancellationToken) => + { + using var reader = new RemoteHostAssetReader(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg); + await reader.ReadDataAsync(cancellationToken); + }, cancellationToken), cancellationToken).ConfigureAwait(false); } From c67ce6a424bb5c83377b8e5c69254062091dacf0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:03:00 -0700 Subject: [PATCH 0443/1047] Switch to full types --- .../Core/RemoteHostAssetSerialization.cs | 148 ++++++++---------- .../Remote/Core/SolutionAssetProvider.cs | 2 +- .../ServiceHub/Host/SolutionAssetSource.cs | 6 +- 3 files changed, 63 insertions(+), 93 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 5137d591c5307..ee194460c8da5 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.IO.Pipelines; using System.Threading; using System.Threading.Channels; @@ -56,7 +55,12 @@ namespace Microsoft.CodeAnalysis.Remote; /// 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 : IDisposable +internal readonly struct RemoteHostAssetWriter( + PipeWriter pipeWriter, + SolutionAssetStorage.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 @@ -78,37 +82,11 @@ namespace Microsoft.CodeAnalysis.Remote; SingleWriter = true, }; - private readonly PipeWriter _pipeWriter; - private readonly SolutionAssetStorage.Scope _scope; - private readonly AssetPath _assetPath; - private readonly ReadOnlyMemory _checksums; - private readonly ISerializerService _serializer; - - private readonly PooledObject _pooledStream; - private readonly SerializableBytes.ReadWriteStream TempStream => _pooledStream.Object; - private readonly ObjectWriter _writer; - - public RemoteHostAssetWriter(PipeWriter pipeWriter, SolutionAssetStorage.Scope scope, AssetPath assetPath, ReadOnlyMemory checksums, ISerializerService serializer) - { - _pipeWriter = pipeWriter; - _scope = scope; - _assetPath = assetPath; - _checksums = checksums; - _serializer = serializer; - - // 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. - _pooledStream = s_streamPool.GetPooledObject(); - _writer = new ObjectWriter(_pooledStream.Object, leaveOpen: true, writeValidationBytes: false); - } - - public void Dispose() - { - _writer.Dispose(); - _pooledStream.Dispose(); - } + private readonly PipeWriter _pipeWriter = pipeWriter; + private readonly SolutionAssetStorage.Scope _scope = scope; + private readonly AssetPath _assetPath = assetPath; + private readonly ReadOnlyMemory _checksums = checksums; + private readonly ISerializerService _serializer = serializer; public async ValueTask WriteDataAsync(CancellationToken cancellationToken) { @@ -174,6 +152,13 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync( { await Task.Yield(); + // 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. @@ -188,7 +173,7 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync( while (channelReader.TryRead(out var item)) { await WriteSingleAssetToPipeAsync( - item.checksum, item.asset, cancellationToken).ConfigureAwait(false); + pooledStream.Object, objectWriter, item.checksum, item.asset, cancellationToken).ConfigureAwait(false); foundChecksumCount++; } } @@ -200,6 +185,8 @@ await WriteSingleAssetToPipeAsync( } private async ValueTask WriteSingleAssetToPipeAsync( + SerializableBytes.ReadWriteStream tempStream, + ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) @@ -213,18 +200,18 @@ private async ValueTask WriteSingleAssetToPipeAsync( // 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(checksum, asset, cancellationToken); + 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(); + 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. - this.TempStream.Position = 0; - await this.TempStream.CopyToAsync(_pipeWriter, cancellationToken).ConfigureAwait(false); + 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 @@ -234,27 +221,27 @@ private async ValueTask WriteSingleAssetToPipeAsync( } private void WriteAssetToTempStream( - Checksum checksum, object asset, CancellationToken cancellationToken) + SerializableBytes.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. - this.TempStream.Position = 0; - this.TempStream.SetLength(0, truncate: false); + 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. - _writer.WriteValidationBytes(); + objectWriter.WriteValidationBytes(); // Write the checksum for the asset we're writing out, so the other side knows what asset this is. - checksum.WriteTo(_writer); + checksum.WriteTo(objectWriter); // Write out the kind so the receiving end knows how to deserialize this asset. - _writer.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); + objectWriter.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); // Now serialize out the asset itself. - _serializer.Serialize(asset, _writer, _scope.ReplicationContext, cancellationToken); + _serializer.Serialize(asset, objectWriter, _scope.ReplicationContext, cancellationToken); } private void WriteSentinelByteToPipeWriter() @@ -264,9 +251,9 @@ private void WriteSentinelByteToPipeWriter() _pipeWriter.Advance(1); } - private void WriteTempStreamLengthToPipeWriter() + private void WriteTempStreamLengthToPipeWriter(SerializableBytes.ReadWriteStream tempStream) { - var length = this.TempStream.Length; + var length = tempStream.Length; Contract.ThrowIfTrue(length > int.MaxValue); var span = _pipeWriter.GetSpan(sizeof(int)); @@ -275,41 +262,20 @@ private void WriteTempStreamLengthToPipeWriter() } } -internal readonly struct RemoteHostAssetReader : IDisposable +internal readonly struct RemoteHostAssetReader( + PipeReader pipeReader, + Checksum solutionChecksum, + int objectCount, + ISerializerService serializer, + Action callback, + TArg arg) { - private readonly PipeReader _pipeReader; - private readonly Checksum _solutionChecksum; - private readonly int _objectCount; - private readonly ISerializerService _serializer; - private readonly Action _callback; - private readonly TArg _arg; - - private readonly Stream _pipeReaderStream; - private readonly ObjectReader _reader; - - public RemoteHostAssetReader( - PipeReader pipeReader, Checksum solutionChecksum, int objectCount, ISerializerService serializer, Action callback, TArg arg) - { - _pipeReader = pipeReader; - _solutionChecksum = solutionChecksum; - _objectCount = objectCount; - _serializer = serializer; - _callback = callback; - _arg = arg; - - _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. - _reader = ObjectReader.GetReader(_pipeReaderStream, leaveOpen: true, checkValidationBytes: false); - } - - public void Dispose() - { - _pipeReaderStream.Dispose(); - _reader.Dispose(); - } + 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) { @@ -325,6 +291,13 @@ public ValueTask ReadDataAsync(CancellationToken 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); @@ -332,10 +305,11 @@ private async ValueTask ReadDataSuppressedFlowAsync(CancellationToken cancellati // Now actually read all the messages we expect to get. for (int i = 0, n = _objectCount; i < n; i++) - await ReadSingleMessageAsync(cancellationToken).ConfigureAwait(false); + await ReadSingleMessageAsync(objectReader, cancellationToken).ConfigureAwait(false); } - private async ValueTask ReadSingleMessageAsync(CancellationToken cancellationToken) + 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); @@ -351,14 +325,14 @@ private async ValueTask ReadSingleMessageAsync(CancellationToken cancellationTok _pipeReader.AdvanceTo(fillReadResult.Buffer.Start); // Let the object reader do it's own individual object checking. - _reader.CheckValidationBytes(); + 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(_reader); - var kind = (WellKnownSynchronizationKind)_reader.ReadByte(); + var checksum = Checksum.ReadFrom(objectReader); + var kind = (WellKnownSynchronizationKind)objectReader.ReadByte(); - var asset = _serializer.Deserialize(kind, _reader, cancellationToken); + var asset = _serializer.Deserialize(kind, objectReader, cancellationToken); Contract.ThrowIfNull(asset); _callback(checksum, (T)asset, _arg); } diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index 36a8952a424cf..a68684451957c 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -49,7 +49,7 @@ async ValueTask WriteAssetsSuppressedFlowAsync(PipeWriter pipeWriter, Checksum s try { var scope = _assetStorage.GetScope(solutionChecksum); - using var writer = new RemoteHostAssetWriter(pipeWriter, scope, assetPath, checksums, _serializer); + var writer = new RemoteHostAssetWriter(pipeWriter, scope, assetPath, checksums, _serializer); await writer.WriteDataAsync(cancellationToken).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index dc6df985fc5e3..153212aa59e42 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -33,11 +33,7 @@ await RemoteCallback.InvokeServiceAsync( SolutionAssetProvider.ServiceDescriptor, (callback, cancellationToken) => callback.InvokeAsync( (proxy, pipeWriter, cancellationToken) => proxy.WriteAssetsAsync(pipeWriter, solutionChecksum, assetPath, checksums, cancellationToken), - async (pipeReader, cancellationToken) => - { - using var reader = new RemoteHostAssetReader(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg); - await reader.ReadDataAsync(cancellationToken); - }, + (pipeReader, cancellationToken) => new RemoteHostAssetReader(pipeReader, solutionChecksum, checksums.Length, serializerService, assetCallback, arg).ReadDataAsync(cancellationToken), cancellationToken), cancellationToken).ConfigureAwait(false); } From 1a45178c14d46556124b0591ad64e0a2b82e5ecd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:07:07 -0700 Subject: [PATCH 0444/1047] Apply suggestions from code review --- .../Core/RemoteHostAssetSerialization.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index ee194460c8da5..d1d05e3d42403 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -116,8 +116,7 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); } - private async Task FindAssetsFromScopeAndWriteToChannelAsync( - ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) + private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) { Exception? exception = null; try @@ -125,15 +124,13 @@ private async Task FindAssetsFromScopeAndWriteToChannelAsync( await Task.Yield(); await _scope.FindAssetsAsync( - _assetPath, - _checksums, + _assetPath, _checksums, // 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 FindAssetsAsync completed) or if cancellation // happens. 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. static (checksum, asset, channelWriter) => channelWriter.TryWrite((checksum, asset)), - channelWriter, - cancellationToken).ConfigureAwait(false); + channelWriter, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { @@ -185,11 +182,7 @@ await WriteSingleAssetToPipeAsync( } private async ValueTask WriteSingleAssetToPipeAsync( - SerializableBytes.ReadWriteStream tempStream, - ObjectWriter objectWriter, - Checksum checksum, - object asset, - CancellationToken cancellationToken) + SerializableBytes.ReadWriteStream tempStream, ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) { Contract.ThrowIfNull(asset); @@ -308,8 +301,7 @@ private async ValueTask ReadDataSuppressedFlowAsync(CancellationToken cancellati await ReadSingleMessageAsync(objectReader, cancellationToken).ConfigureAwait(false); } - private async ValueTask ReadSingleMessageAsync( - ObjectReader objectReader, CancellationToken cancellationToken) + 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); From cdaa567a42c5ca24df36ca25a4c06c5c6f6f24bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:08:41 -0700 Subject: [PATCH 0445/1047] Simplify --- .../Remote/Core/RemoteHostAssetSerialization.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index d1d05e3d42403..f66f0f37c657b 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -18,6 +18,7 @@ namespace Microsoft.CodeAnalysis.Remote; using ChecksumChannel = Channel<(Checksum checksum, object asset)>; using ChecksumChannelReader = ChannelReader<(Checksum checksum, object asset)>; using ChecksumChannelWriter = ChannelWriter<(Checksum checksum, object asset)>; +using static SerializableBytes; /// /// Contains the utilities for writing assets from the host to a pipe-writer and for reading those assets on the @@ -69,7 +70,7 @@ internal readonly struct RemoteHostAssetWriter( /// public const byte MessageSentinelByte = 0b10010000; - private static readonly ObjectPool s_streamPool = new(SerializableBytes.CreateWritableStream); + private static readonly ObjectPool s_streamPool = new(CreateWritableStream); private static readonly UnboundedChannelOptions s_channelOptions = new() { @@ -182,7 +183,7 @@ await WriteSingleAssetToPipeAsync( } private async ValueTask WriteSingleAssetToPipeAsync( - SerializableBytes.ReadWriteStream tempStream, ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) + ReadWriteStream tempStream, ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) { Contract.ThrowIfNull(asset); @@ -214,7 +215,7 @@ private async ValueTask WriteSingleAssetToPipeAsync( } private void WriteAssetToTempStream( - SerializableBytes.ReadWriteStream tempStream, ObjectWriter objectWriter, Checksum checksum, object asset, CancellationToken cancellationToken) + 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 @@ -244,7 +245,7 @@ private void WriteSentinelByteToPipeWriter() _pipeWriter.Advance(1); } - private void WriteTempStreamLengthToPipeWriter(SerializableBytes.ReadWriteStream tempStream) + private void WriteTempStreamLengthToPipeWriter(ReadWriteStream tempStream) { var length = tempStream.Length; Contract.ThrowIfTrue(length > int.MaxValue); From 285787f0b64cd0964ab67c15fe96debe250f60a2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:09:10 -0700 Subject: [PATCH 0446/1047] Simplify --- src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs index 25d325d6565fa..f425a52edcacb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.IO.Pipelines; @@ -11,8 +10,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Threading; -using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; From 0ff83b207fbe868e524cdd9a0a6b9e4aca3a177d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:11:50 -0700 Subject: [PATCH 0447/1047] Simplify --- .../Core/RemoteHostAssetSerialization.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index f66f0f37c657b..d21d24b3931f3 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -28,21 +28,16 @@ namespace Microsoft.CodeAnalysis.Remote; /// | 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 / +/// 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: /// /// ---------------------------------------------------------------------------------------------------------- @@ -51,10 +46,10 @@ namespace Microsoft.CodeAnalysis.Remote; /// | 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. +/// 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, From 260be33ee6b5c08b5837eebe17afc413844f35a7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:12:39 -0700 Subject: [PATCH 0448/1047] Simplify --- .../Remote/Core/RemoteHostAssetSerialization.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index d21d24b3931f3..5c3a06b8e0519 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -19,6 +19,7 @@ namespace Microsoft.CodeAnalysis.Remote; using ChecksumChannelReader = ChannelReader<(Checksum checksum, object asset)>; using ChecksumChannelWriter = ChannelWriter<(Checksum checksum, object asset)>; using static SerializableBytes; +using static SolutionAssetStorage; /// /// Contains the utilities for writing assets from the host to a pipe-writer and for reading those assets on the @@ -52,11 +53,7 @@ namespace Microsoft.CodeAnalysis.Remote; /// invoke. Finally, the asset data itself is written out. /// internal readonly struct RemoteHostAssetWriter( - PipeWriter pipeWriter, - SolutionAssetStorage.Scope scope, - AssetPath assetPath, - ReadOnlyMemory checksums, - ISerializerService serializer) + 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 @@ -79,7 +76,7 @@ internal readonly struct RemoteHostAssetWriter( }; private readonly PipeWriter _pipeWriter = pipeWriter; - private readonly SolutionAssetStorage.Scope _scope = scope; + private readonly Scope _scope = scope; private readonly AssetPath _assetPath = assetPath; private readonly ReadOnlyMemory _checksums = checksums; private readonly ISerializerService _serializer = serializer; From 27b3d38a5454329c5f8acc864f1b0062a53c606f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:14:23 -0700 Subject: [PATCH 0449/1047] Simplify --- .../Remote/Core/RemoteHostAssetSerialization.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 5c3a06b8e0519..3a36cf7f9a6b7 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -15,9 +15,7 @@ namespace Microsoft.CodeAnalysis.Remote; -using ChecksumChannel = Channel<(Checksum checksum, object asset)>; -using ChecksumChannelReader = ChannelReader<(Checksum checksum, object asset)>; -using ChecksumChannelWriter = ChannelWriter<(Checksum checksum, object asset)>; +using ChecksumAndAsset = (Checksum checksum, object asset); using static SerializableBytes; using static SolutionAssetStorage; @@ -86,13 +84,13 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) // Create a channel to communicate between the searching and writing tasks. This allows the searching task to // find items, add them to the channel synchronously, and immediately continue searching for more items. // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. - var channel = Channel.CreateUnbounded<(Checksum checksum, object asset)>(s_channelOptions); + var channel = Channel.CreateUnbounded(s_channelOptions); // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets // to the pipe. Capture-free version is only available on netcore unfortunately. #if NET using var _ = cancellationToken.Register( - static (obj, cancellationToken) => ((ChecksumChannel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), + static (obj, cancellationToken) => ((Channel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), state: channel); #else using var _ = cancellationToken.Register( @@ -109,7 +107,7 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); } - private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChecksumChannelWriter channelWriter, CancellationToken cancellationToken) + private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChannelWriter channelWriter, CancellationToken cancellationToken) { Exception? exception = null; try @@ -137,8 +135,7 @@ await _scope.FindAssetsAsync( } } - private async Task ReadAssetsFromChannelAndWriteToPipeAsync( - ChecksumChannelReader channelReader, CancellationToken cancellationToken) + private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader channelReader, CancellationToken cancellationToken) { await Task.Yield(); From 51ce5751fdc5f9f03c1a21f7fc9fd61cedfded2f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:14:42 -0700 Subject: [PATCH 0450/1047] Simplify --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 3a36cf7f9a6b7..cd245f9182f9c 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -15,9 +15,9 @@ namespace Microsoft.CodeAnalysis.Remote; -using ChecksumAndAsset = (Checksum checksum, object asset); 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 From c796d4c6633dc0054f010d2316cb78ba37f78883 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:15:30 -0700 Subject: [PATCH 0451/1047] Simplify --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index cd245f9182f9c..778613819bb3b 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -88,12 +88,11 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets // to the pipe. Capture-free version is only available on netcore unfortunately. -#if NET using var _ = cancellationToken.Register( +#if NET static (obj, cancellationToken) => ((Channel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), state: channel); #else - using var _ = cancellationToken.Register( () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); #endif From cb89ca905dfbebd25cfb59ed92ba5927dd6f1b78 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:16:48 -0700 Subject: [PATCH 0452/1047] Simplify --- .../Remote/Core/RemoteHostAssetSerialization.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 778613819bb3b..15ad40c072144 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -96,14 +96,11 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); #endif - // Spin up a task to go search for all the requested checksums, adding results to the channel. - var findAssetsTask = FindAssetsFromScopeAndWriteToChannelAsync(channel.Writer, cancellationToken); - - // Spin up a task to read from the channel and write out the assets to the pipe-writer. - var writeAssetsTask = ReadAssetsFromChannelAndWriteToPipeAsync(channel.Reader, cancellationToken); - - // Wait for both the searching and writing tasks to finish. - await Task.WhenAll(findAssetsTask, writeAssetsTask).ConfigureAwait(false); + // Spin up a task to go find all the requested checksums, adding results to the channel. + // Spin up a task to read from the channel, writing out the assets to the pipe-writer. + await Task.WhenAll( + FindAssetsFromScopeAndWriteToChannelAsync(channel.Writer, cancellationToken), + ReadAssetsFromChannelAndWriteToPipeAsync(channel.Reader, cancellationToken)).ConfigureAwait(false); } private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChannelWriter channelWriter, CancellationToken cancellationToken) From dfbe833cb883046c524d146c8172bf333a1ffeb2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:55:22 -0700 Subject: [PATCH 0453/1047] Simplify --- src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs index 15ad40c072144..e54af72907a35 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs @@ -283,7 +283,7 @@ private async ValueTask ReadDataSuppressedFlowAsync(CancellationToken cancellati Contract.ThrowIfFalse(_solutionChecksum == responseSolutionChecksum); // Now actually read all the messages we expect to get. - for (int i = 0, n = _objectCount; i < n; i++) + for (var i = 0; i < _objectCount; i++) await ReadSingleMessageAsync(objectReader, cancellationToken).ConfigureAwait(false); } From 8c56b9e98e5144a648fa49433af21af0132d7f4d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:56:25 -0700 Subject: [PATCH 0454/1047] berak into files --- .../Remote/Core/RemoteHostAssetReader.cs | 129 ++++++++++++++++++ ...ialization.cs => RemoteHostAssetWriter.cs} | 117 ---------------- 2 files changed, 129 insertions(+), 117 deletions(-) create mode 100644 src/Workspaces/Remote/Core/RemoteHostAssetReader.cs rename src/Workspaces/Remote/Core/{RemoteHostAssetSerialization.cs => RemoteHostAssetWriter.cs} (68%) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs new file mode 100644 index 0000000000000..228f3cd0469ed --- /dev/null +++ b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Serialization; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Remote; + +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. 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 + // from within ObjectReader.GetReader 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/RemoteHostAssetWriter.cs similarity index 68% rename from src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs rename to src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index e54af72907a35..c925f9c57ae8d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetSerialization.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; using System.Buffers.Binary; using System.IO.Pipelines; using System.Threading; @@ -240,119 +239,3 @@ private void WriteTempStreamLengthToPipeWriter(ReadWriteStream tempStream) _pipeWriter.Advance(sizeof(int)); } } - -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. 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 - // from within ObjectReader.GetReader 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); - } -} From f92a417b6ea9432a89e8f174dfbbadcda528c9c4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:56:48 -0700 Subject: [PATCH 0455/1047] Simplify --- src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index 153212aa59e42..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; From ea4f760aa73ee496b135d7da667000a86424d60a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 20:57:44 -0700 Subject: [PATCH 0456/1047] Simplify --- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 2 +- .../Compiler/Core/Utilities/SerializableBytes.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index c925f9c57ae8d..37cd371ab2dfc 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -59,7 +59,7 @@ internal readonly struct RemoteHostAssetWriter( /// public const byte MessageSentinelByte = 0b10010000; - private static readonly ObjectPool s_streamPool = new(CreateWritableStream); + private static readonly ObjectPool s_streamPool = new(() => new()); private static readonly UnboundedChannelOptions s_channelOptions = new() { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs index 7c8b5ec36804c..b0e1e7f090ca1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SerializableBytes.cs @@ -90,7 +90,7 @@ private static void BlowChunks(byte[][]? chunks) } internal static ReadWriteStream CreateWritableStream() - => new ReadWriteStream(); + => new(); public abstract class PooledStream : Stream { From 5f3e2306c8d1ac7d6b53e0459c9fc4b6607ab0ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 21:56:01 -0700 Subject: [PATCH 0457/1047] Docs --- src/Workspaces/Remote/Core/RemoteHostAssetReader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs index 228f3cd0469ed..c1f9cbcefa891 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs @@ -12,6 +12,10 @@ 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. +/// internal readonly struct RemoteHostAssetReader( PipeReader pipeReader, Checksum solutionChecksum, From ed89ebf4ddca18b8087050df5fde939779525f87 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 14 Apr 2024 21:56:22 -0700 Subject: [PATCH 0458/1047] Docs --- src/Workspaces/Remote/Core/RemoteHostAssetReader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs index c1f9cbcefa891..b2cc72f6fc716 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs @@ -14,7 +14,8 @@ 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. +/// 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, From 412b744e959cecf0147aadad160a02d4c4912fa6 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 15 Apr 2024 08:48:28 +0300 Subject: [PATCH 0459/1047] Simplify test markup --- .../MakeAnonymousFunctionStaticTests.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs index d08492d3bc96c..d3e2d6a664fa5 100644 --- a/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeAnonymousFunctionStatic/MakeAnonymousFunctionStaticTests.cs @@ -129,7 +129,7 @@ class C { void M() { - N({|IDE0320:{{anonymousFunctionSyntax}}|}); + N([|{{anonymousFunctionSyntax}}|]); } void N(Action a) @@ -199,7 +199,7 @@ class C void M() { - N({|IDE0320:{{anonymousFunctionSyntax}}|}); + N([|{{anonymousFunctionSyntax}}|]); } void N(Func f) @@ -267,7 +267,7 @@ class C { void M(int i) { - N({|IDE0320:{{anonymousFunctionSyntax}}|}); + N([|{{anonymousFunctionSyntax}}|]); } void N(Func f) @@ -335,7 +335,7 @@ class C void M() { int i = 0; - N({|IDE0320:{{anonymousFunctionSyntax}}|}); + N([|{{anonymousFunctionSyntax}}|]); } void N(Func f) @@ -374,10 +374,10 @@ class C { void M() { - N({|IDE0320:() => + N([|() => { - Action a = {|IDE0320:() => { }|}; - }|}); + Action a = [|() => { }|]; + }|]); } void N(Action a) @@ -418,10 +418,10 @@ class C { void M() { - N({|IDE0320:delegate () + N([|delegate () { - Action a = {|IDE0320:delegate () { }|}; - }|}); + Action a = [|delegate () { }|]; + }|]); } void N(Action a) From 5700fb1bd7e7c847b78433569a96e93f2f1e0279 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 15 Apr 2024 12:18:13 +0000 Subject: [PATCH 0460/1047] Update dependencies from https://github.com/dotnet/source-build-externals build 20240411.1 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 9.0.0-alpha.1.24208.1 -> To Version 9.0.0-alpha.1.24211.1 From 30ab8c06b7546c8315be32bafcb42ede4132d552 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 15 Apr 2024 12:18:50 +0000 Subject: [PATCH 0461/1047] Update dependencies from https://github.com/dotnet/command-line-api build 20240409.3 Microsoft.SourceBuild.Intermediate.command-line-api , System.CommandLine From Version 0.1.512601 -> To Version 0.1.520903 From 1bf9c2d1b04c5f88370b8b750c665622c57c53f8 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 15 Apr 2024 20:46:29 +0300 Subject: [PATCH 0462/1047] Add undocumented diagnostic entry --- src/Features/RulesMissingDocumentation.md | 1 + 1 file changed, 1 insertion(+) 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 | From 97fc7099b359ade5d030d4efae1961ee9a827ae7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 11:39:21 -0700 Subject: [PATCH 0463/1047] Fix crash in AIOSP when filtering down to projects --- .../Portable/NavigateTo/NavigateToSearcher.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index 85ef7d099d162..424ed5fb706f1 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -254,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 { @@ -450,9 +450,13 @@ await advancedService.SearchCachedDocumentsAsync( } 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 @@ -464,16 +468,24 @@ 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) => { From 839e1abc434ead394e9b984e264d16cec5d267f9 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 15 Apr 2024 12:05:45 -0700 Subject: [PATCH 0464/1047] Add support for checksum-based interceptors (#72814) --- docs/features/interceptors.md | 45 +- .../CSharp/Portable/CSharpExtensions.cs | 20 + .../CSharp/Portable/CSharpResources.resx | 15 + .../Portable/Compilation/CSharpCompilation.cs | 45 +- .../Compilation/CSharpSemanticModel.cs | 29 +- .../CSharp/Portable/Errors/ErrorCode.cs | 6 + .../CSharp/Portable/Errors/ErrorFacts.cs | 5 + .../Lowering/LocalRewriter/LocalRewriter.cs | 2 +- .../LocalRewriter/LocalRewriter_Call.cs | 4 +- ...LocalRewriter_FunctionPointerInvocation.cs | 2 +- .../CSharp/Portable/PublicAPI.Unshipped.txt | 8 + .../SourceMethodSymbolWithAttributes.cs | 184 +++- .../Portable/Utilities/ContentHashComparer.cs | 30 + .../Utilities/InterceptableLocation.cs | 180 ++++ .../Portable/xlf/CSharpResources.cs.xlf | 25 + .../Portable/xlf/CSharpResources.de.xlf | 25 + .../Portable/xlf/CSharpResources.es.xlf | 25 + .../Portable/xlf/CSharpResources.fr.xlf | 25 + .../Portable/xlf/CSharpResources.it.xlf | 25 + .../Portable/xlf/CSharpResources.ja.xlf | 25 + .../Portable/xlf/CSharpResources.ko.xlf | 25 + .../Portable/xlf/CSharpResources.pl.xlf | 25 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 25 + .../Portable/xlf/CSharpResources.ru.xlf | 25 + .../Portable/xlf/CSharpResources.tr.xlf | 25 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 25 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 25 + .../Semantic/Semantics/InterceptorsTests.cs | 789 +++++++++++++++++- .../SourceGeneration/GeneratorDriverTests.cs | 96 +++ .../Attributes/AttributeDescription.cs | 3 +- 30 files changed, 1734 insertions(+), 54 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Utilities/ContentHashComparer.cs create mode 100644 src/Compilers/CSharp/Portable/Utilities/InterceptableLocation.cs diff --git a/docs/features/interceptors.md b/docs/features/interceptors.md index b96fe71e646b6..b0b8597f4f3f7 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 { } } @@ -66,29 +66,26 @@ Any "ordinary method" (i.e. with `MethodKind.Ordinary`) can have its calls inter 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 +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. -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. +#### Location encoding -`#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. +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. -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. - -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 a convenience 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. ### Non-invocation method usages @@ -103,7 +100,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 +112,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 +133,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!; } ``` 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 35d9967120efb..cd8a8755867fc 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7908,4 +7908,19 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 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}'. + 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 4a54eece1b5e2..b79df55c6af72 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -5208,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/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 0b141c96fe9f6..15fee745c96a3 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2303,6 +2303,12 @@ internal enum ErrorCode ERR_NoModifiersOnUsing = 9229, ERR_CannotDynamicInvokeOnExpression = 9230, + ERR_InterceptsLocationDataInvalidFormat = 9231, + ERR_InterceptsLocationUnsupportedVersion = 9232, + ERR_InterceptsLocationDuplicateFile = 9233, + ERR_InterceptsLocationFileNotFound = 9234, + ERR_InterceptsLocationDataInvalidPosition = 9235, + #endregion // Note: you will need to do the following after adding errors: diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 2342c333dfb1f..e634deae8b73d 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2432,6 +2432,11 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) 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: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 30707d0245d8f..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); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 956ff4e6071c1..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); 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/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index a7eede3cf18d3..e2eac632d971c 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,5 +1,9 @@ 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! @@ -17,4 +21,8 @@ static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetElementConversion(this 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/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index c23defaca34cc..a4c5ed6fff70e 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,184 @@ 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: InvocationExpressionSyntax invocation } simpleName } when invocation.Expression == simpleName: + // happy case + break; + case { Parent: SimpleNameSyntax { Parent: not MemberAccessExpressionSyntax } }: + case { Parent: SimpleNameSyntax { Parent: MemberAccessExpressionSyntax memberAccess } rhs } when memberAccess.Name == rhs: + // 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 +1138,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 +1265,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/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 94e72ea2f90a1..9cb6c6666dea2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1102,6 +1102,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}. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 002d1aceb7570..5350d1c59acab 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1102,6 +1102,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. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 700e8f5912984..2a449abc1f16c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1102,6 +1102,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}" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index b5d7cacbfdb09..e39c57737603f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1102,6 +1102,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}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 920818aa35c52..d3378e3228a1f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1102,6 +1102,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}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 9ccc403201939..88b8066444024 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1102,6 +1102,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}' で実装できません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 736c1e7ed0f2c..b964b1a1d24fd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1102,6 +1102,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}'을(를) 구현할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index e05459fae3648..c7aceef3e3fc0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1102,6 +1102,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}" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 528dfbfdf5076..eca4a0f9e327d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1102,6 +1102,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}' diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 14c4a7c5ce8fc..f00022bc0283a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1102,6 +1102,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}" diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 45e26b5b7c14f..3104df55006e0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1102,6 +1102,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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index bd7eb252201c2..4a501f24b6625 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1102,6 +1102,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}” diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index bf6487794259d..94d77f41046f6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1102,6 +1102,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}' diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs index b1cf3f2f8a850..63b63f19b1d28 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,416 @@ 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()); + } } 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/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 }; From c4466f5c964313f85bc56bb9cf6b442495c93cb4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 12:27:04 -0700 Subject: [PATCH 0465/1047] Add test --- .../NavigateTo/NavigateToSearcherTests.cs | 107 +++++++++++++++++- .../NavigateTo/AbstractNavigateToTests.cs | 36 +----- ...ActiveAndVisibleDocumentTrackingService.cs | 47 ++++++++ .../AbstractNavigateToSearchService.cs | 6 +- 4 files changed, 154 insertions(+), 42 deletions(-) create mode 100644 src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 675023c8d2392..24c066414e32d 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.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo; 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,8 +22,10 @@ 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(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); + private static void SetupSearchProject( Mock searchService, string pattern, @@ -293,7 +294,103 @@ public async Task DoNotCrashWithoutSearchService() await searcher.SearchAsync(NavigateToSearchScope.Solution, CancellationToken.None); } - private class TestNavigateToSearchResult(EditorTestWorkspace workspace, TextSpan sourceSpan) + [Fact] + public async Task ProjectScopeSearchingOnlySearchesSingleProjectForGeneratedDocuments() + { + using var workspace = EditorTestWorkspace.Create(""" + + + + public class C + { + } + + + + + public class D + { + } + + + + """, composition: FirstActiveAndVisibleComposition); + + var pattern = "irrelevant"; + var result = 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.AddItemAsync(It.IsAny(), result, 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + { + OnSearchCachedDocumentsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } + + public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func onResultFound, CancellationToken cancellationToken) + { + OnSearchDocumentsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } + + public Task SearchGeneratedDocumentsAsync(Solution solution, ImmutableArray projects, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + { + OnSearchProjectsAsyncCalled?.Invoke(); + return Task.CompletedTask; + } + } + + 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/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index 5685f62e13433..b9f70d5b16fc5 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -37,7 +37,7 @@ 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)); @@ -263,40 +263,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/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs new file mode 100644 index 0000000000000..08628f9c6e68e --- /dev/null +++ b/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo; + +internal sealed 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); + } +} diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index ddb6e6066701e..b9015db743520 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -15,7 +15,7 @@ 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,7 +27,9 @@ 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; From 90684049737a45905c1b9e3e0da7a4b487c740d7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 12:52:57 -0700 Subject: [PATCH 0466/1047] Fixup comment --- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 37cd371ab2dfc..951cb1e7701b9 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -67,8 +67,8 @@ internal readonly struct RemoteHostAssetWriter( // channel to operate in a more efficient manner knowing it won't have to synchronize data for multiple readers. SingleReader = true, - // Currently we only have a single writer writing to the channel when we call FindAllAssetsAsync. However, we - // could change this in the future to allow the search to happen in parallel. + // Currently we only have a single writer writing to the channel when we call _scope.FindAssetsAsync. However, + // we could change this in the future to allow the search to happen in parallel. SingleWriter = true, }; From 770500bc1e182abc5f76845b49eff7ce92766ed3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 12:54:02 -0700 Subject: [PATCH 0467/1047] Docs --- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 951cb1e7701b9..8c9eb4da85aac 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -112,9 +112,9 @@ private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChannelWriter channelWriter.TryWrite((checksum, asset)), channelWriter, cancellationToken).ConfigureAwait(false); } From 44fce3063da010289162dfa23a46ce27dc3a9113 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 12:55:53 -0700 Subject: [PATCH 0468/1047] clarification comments --- .../SQLite/v2/SQLitePersistentStorage_Threading.cs | 10 ++++++---- src/Workspaces/Remote/Core/RemoteHostAssetReader.cs | 5 +++-- src/Workspaces/Remote/Core/SolutionAssetProvider.cs | 5 +++-- 3 files changed, 12 insertions(+), 8 deletions(-) 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..72edf6a941550 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs @@ -34,8 +34,9 @@ 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); } @@ -48,8 +49,9 @@ 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); } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs index b2cc72f6fc716..b3a6baeb92337 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs @@ -38,8 +38,9 @@ public ValueTask ReadDataAsync(CancellationToken cancellationToken) // 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 + // 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); } diff --git a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs index a68684451957c..9ed631830ecc8 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetProvider.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetProvider.cs @@ -35,8 +35,9 @@ public ValueTask WriteAssetsAsync( // 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 + // 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); From b42de8bce88ec595f3f2eede83b3727a100c499c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 12:57:21 -0700 Subject: [PATCH 0469/1047] Comments --- .../Remote/Core/RemoteHostAssetReader.cs | 20 +++++----- .../Remote/Core/RemoteHostAssetWriter.cs | 38 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs index b3a6baeb92337..7bedab5bb1271 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetReader.cs @@ -54,8 +54,8 @@ private async ValueTask ReadDataSuppressedFlowAsync(CancellationToken cancellati // 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. + // 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); @@ -69,14 +69,14 @@ private async ValueTask ReadSingleMessageAsync(ObjectReader objectReader, Cancel // 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. + // 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 - // from within ObjectReader.GetReader below. + // 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. @@ -106,8 +106,8 @@ private async ValueTask CheckSentinelByteAndReadLengthAsync(CancellationTok 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). + // 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); diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 8c9eb4da85aac..ca05cccdec70f 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -85,8 +85,8 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. var channel = Channel.CreateUnbounded(s_channelOptions); - // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets - // to the pipe. Capture-free version is only available on netcore unfortunately. + // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets to + // the pipe. 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)), @@ -135,15 +135,15 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader Date: Mon, 15 Apr 2024 13:34:11 -0700 Subject: [PATCH 0470/1047] Move type into its own file --- .../AbstractDocumentPullDiagnosticHandler.cs | 24 + .../AbstractPullDiagnosticHandler.cs | 918 +++++++++--------- .../DocumentPullDiagnosticHandler.cs | 233 +++-- .../DocumentPullDiagnosticHandlerFactory.cs | 41 +- .../Diagnostics/PullDiagnosticCategories.cs | 51 +- .../WorkspacePullDiagnosticHandler.cs | 95 +- .../WorkspacePullDiagnosticHandlerFactory.cs | 27 +- 7 files changed, 697 insertions(+), 692 deletions(-) create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs 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..dc20144b584f1 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.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 Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CommonLanguageServerProtocol.Framework; +using Roslyn.LanguageServer.Protocol; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : AbstractPullDiagnosticHandler( + diagnosticAnalyzerService, + diagnosticRefresher, + globalOptions), ITextDocumentIdentifierHandler + where TDiagnosticsParams : IPartialResultParams +{ + public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index d24329c95636b..755e5d771299d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -20,577 +20,563 @@ 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; + // 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) - : AbstractPullDiagnosticHandler( - diagnosticAnalyzerService, - diagnosticRefresher, - globalOptions), ITextDocumentIdentifierHandler - where TDiagnosticsParams : IPartialResultParams { - public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + DiagnosticAnalyzerService = diagnosticAnalyzerService; + _diagnosticRefresher = diagnosticRefresher; + GlobalOptions = globalOptions; } + protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; + + /// + /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. Also + /// used so we can report which documents were removed and can have all their diagnostics cleared. + /// + 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); + /// - /// Root type for both document and workspace diagnostic pull requests. + /// 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? 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) { - /// - /// 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 - { - Contract.ThrowIfNull(context.Solution); + var clientCapabilities = context.GetRequiredClientCapabilities(); + var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; + var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; + var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; + context.TraceInformation($"{handlerName} started getting diagnostics"); - 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 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. - using var _1 = PooledDictionary.GetInstance(out var documentIdToPreviousDiagnosticParams); - using var _2 = PooledHashSet.GetInstance(out var removedDocuments); - ProcessPreviousResults(context.Solution, previousResults, documentIdToPreviousDiagnosticParams, removedDocuments); + // 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, removedDocuments, 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, 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); - // 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) { - 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) - { - 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 = documentIdToPreviousDiagnosticParams[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()}"); - // 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) + // 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); + } + } + + // 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)) { - if (!seenDiagnosticSourceIds.Contains(projectOrDocumentId) && - !removedDocuments.Contains(previousDiagnosticParams)) - { - progress.Report(CreateRemovedReport(previousDiagnosticParams.TextDocument)); - } + progress.Report(CreateRemovedReport(previousDiagnosticParams.TextDocument)); } + } - // Clear out the solution context to avoid retaining memory - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1809058 - context.ClearSolutionContext(); + // 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); + // 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"); - } + // 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"); + } - return CreateReturn(progress); + return CreateReturn(progress); - static void ProcessPreviousResults( - Solution solution, - ImmutableArray previousResults, - Dictionary idToPreviousDiagnosticParams, - HashSet removedResults) + static void ProcessPreviousResults( + Solution solution, + ImmutableArray previousResults, + Dictionary idToPreviousDiagnosticParams, + HashSet removedResults) + { + foreach (var diagnosticParams in previousResults) { - foreach (var diagnosticParams in previousResults) + if (diagnosticParams.TextDocument != null) { - if (diagnosticParams.TextDocument != null) + var id = GetIdForPreviousResult(diagnosticParams.TextDocument, solution); + if (id != null) { - var id = GetIdForPreviousResult(diagnosticParams.TextDocument, solution); - if (id != null) - { - 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. - removedResults.Add(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. + removedResults.Add(diagnosticParams); } } } + } - 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(DiagnosticAnalyzerService, 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, HashSet 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/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index e9559f60a0123..37196ad7f0177 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -11,149 +11,148 @@ 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, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(analyzerService, diagnosticRefresher, 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 bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) - { - report = CreateReport(identifier, diagnostics: null, resultId); - return true; - } + protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.TextDocument; + + 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, } + ]; + + protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - return null; + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } + + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + { + return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); } - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); + // The client didn't provide us with a previous result to look for, so we can't lookup anything. + return null; + } + + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + 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 { - var category = diagnosticsParams.QueryingDiagnosticKind?.Value; + 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)); + } - if (category == PullDiagnosticCategories.Task) - return new(GetDiagnosticSources(diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, GlobalOptions)); + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); + } - 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)); + 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 []; } - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + var document = textDocument as Document; + if (taskList && document is null) { - return progress.GetFlattenedValues(); + context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); + return []; } - internal static ImmutableArray GetDiagnosticSources( - DiagnosticKind diagnosticKind, bool nonLocalDocumentDiagnostics, bool taskList, RequestContext context, IGlobalOptionService globalOptions) + if (!context.IsTracking(textDocument.GetURI())) { - // 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 []; - } + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return []; + } - if (nonLocalDocumentDiagnostics) - return GetNonLocalDiagnosticSources(); + if (nonLocalDocumentDiagnostics) + return GetNonLocalDiagnosticSources(); - return taskList - ? [new TaskListDiagnosticSource(document!, globalOptions)] - : [new DocumentDiagnosticSource(diagnosticKind, textDocument)]; + return taskList + ? [new TaskListDiagnosticSource(document!, globalOptions)] + : [new DocumentDiagnosticSource(diagnosticKind, textDocument)]; - ImmutableArray GetNonLocalDiagnosticSources() - { - Debug.Assert(!taskList); + 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); + // 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 []; + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + return []; - return [new NonLocalDocumentDiagnosticSource(textDocument, ShouldIncludeAnalyzer)]; + return [new NonLocalDocumentDiagnosticSource(textDocument, ShouldIncludeAnalyzer)]; - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); - } + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index 73300e9cc99dd..d731fe70276cd 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -8,28 +8,27 @@ using Microsoft.CodeAnalysis.Host.Mef; 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 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, + IDiagnosticsRefresher diagnosticsRefresher, + IGlobalOptionService globalOptions) + { + _analyzerService = analyzerService; + _diagnosticsRefresher = diagnosticsRefresher; + _globalOptions = globalOptions; } + + public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) + => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticsRefresher, _globalOptions); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs index 5ba6497ed6e30..3a0243609f9b1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs @@ -4,31 +4,30 @@ 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; + + // 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..66893cacf32fb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -10,60 +10,59 @@ 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, - } - ]; +[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; - protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + 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 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[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) + { + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; + } - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - { - // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics - // produced by document diagnostics. - return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); - } + protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); - protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) - { - return progress.GetFlattenedValues(); - } + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + { + // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics + // produced by document diagnostics. + return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); + } - internal override TestAccessor GetTestAccessor() => new(this); + protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); } + + internal override TestAccessor GetTestAccessor() => new(this); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index 1e53ae4d721bd..b928cf51f3910 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -8,21 +8,20 @@ using Microsoft.CodeAnalysis.Host.Mef; 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, + 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, diagnosticsRefresher, globalOptions); } } From 6f7bcffa5983524018c455fbf7afce4c6209d0e9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:45:17 -0700 Subject: [PATCH 0471/1047] Move off of ForegroundThreadAffinitizedObject --- .../Suggestions/SuggestedActionWithNestedFlavors.cs | 9 +++++---- .../Suggestions/SuggestedActions/SuggestedAction.cs | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) 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) { From d23901ea938b0c1a45618045ddef2bc0c5344cd3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:47:23 -0700 Subject: [PATCH 0472/1047] Move off of ForegroundThreadAffinitizedObject --- .../Core.Wpf/IWpfDifferenceViewerExtensions.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) 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; From 7d13bf9b8f19635e35a368ae66bcf7987f6fb885 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:48:21 -0700 Subject: [PATCH 0473/1047] Move off of ForegroundThreadAffinitizedObject --- src/EditorFeatures/Core.Wpf/QuickInfo/LazyToolTip.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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); From 7bb2046af693e24af69766b25a345f5b85341037 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:51:05 -0700 Subject: [PATCH 0474/1047] Move off of ForegroundThreadAffinitizedObject --- .../Core.Wpf/QuickInfo/ProjectionBufferContent.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs index a0bed1d52547c..9cfa06e7b685b 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. From 8c3b0956f083a877bb8177c04b12dec2a4220bee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:52:24 -0700 Subject: [PATCH 0475/1047] Move off of ForegroundThreadAffinitizedObject --- .../AbstractSignatureHelpCommandHandler.cs | 11 +++++------ ...SignatureHelpBeforeCompletionCommandHandler.cs | 15 ++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) 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/SignatureHelpBeforeCompletionCommandHandler.cs b/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpBeforeCompletionCommandHandler.cs index 8194fc90a5852..59688bdaa08f8 100644 --- a/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpBeforeCompletionCommandHandler.cs +++ b/src/EditorFeatures/Core.Wpf/SignatureHelp/SignatureHelpBeforeCompletionCommandHandler.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.Composition; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; @@ -61,7 +62,7 @@ public SignatureHelpBeforeCompletionCommandHandler( private bool TryGetControllerCommandHandler(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); } } From 4fbfa6b7b975fcd90c442d7fed0b6adb84914d10 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:54:34 -0700 Subject: [PATCH 0476/1047] Move off of ForegroundThreadAffinitizedObject --- ...Presenter.SignatureHelpPresenterSession.cs | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) 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) { From d786d1b118eb878ea0f0f09f65192a0bcacfe038 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:56:09 -0700 Subject: [PATCH 0477/1047] Move off of ForegroundThreadAffinitizedObject --- .../SignatureHelpPresenter.SignatureHelpSource.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 Date: Mon, 15 Apr 2024 13:56:57 -0700 Subject: [PATCH 0478/1047] Move off of ForegroundThreadAffinitizedObject --- .../SignatureHelpControllerProvider.cs | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) 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)); } From 1243da2736bdddd0f63c5bdd1f2c3721cbb0c4ea Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:58:41 -0700 Subject: [PATCH 0479/1047] Remove ThreadingContext entirely --- .../VisualBasic/LineCommit/CommitBufferManager.vb | 10 +--------- .../LineCommit/CommitBufferManagerFactory.vb | 4 +--- 2 files changed, 2 insertions(+), 12 deletions(-) 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 From 2c39591c1fe4e2d64cf0ac930e044d09d327a7d3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 13:59:31 -0700 Subject: [PATCH 0480/1047] Remove ThreadingContext entirely --- ...sualStudioChangeSignatureOptionsService.cs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs b/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs index 2ced2cf36a170..687d1e6d2f242 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 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, From b1f54c88095a216866bef29ee9728d93864b8956 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:00:33 -0700 Subject: [PATCH 0481/1047] Remove ThreadingContext entirely --- .../VisualStudioDesignerAttributeService.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index b629d0bc0b5f9..e616fad9473d5 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; @@ -29,9 +30,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribu [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared] internal class VisualStudioDesignerAttributeService : - ForegroundThreadAffinitizedObject, IDesignerAttributeDiscoveryService.ICallback, IEventListener, IDisposable + 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,8 @@ 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); + _threadingContext.ThrowIfNotOnUIThread(); var designerService = _legacyDesignerService ??= (IVSMDDesignerService)_serviceProvider.GetService(typeof(SVSMDDesignerService)); if (designerService == null) @@ -201,7 +202,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 +277,8 @@ 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); + _threadingContext.ThrowIfNotOnUIThread(); updateService = ComputeUpdateService(); _cpsProjects.TryAdd(projectId, updateService); From ea35f7ce524e9424adb16fd6b8a2308e55e6de0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Mon, 15 Apr 2024 14:06:24 -0700 Subject: [PATCH 0482/1047] Add Hot Reload brokered service registration (#72989) --- src/VisualStudio/Core/Def/PackageRegistration.pkgdef | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 9f22f2350777a..97c945e244fa0 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 From 3df80acb337a65772e2e4dbf50888f7b2893b6cb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:06:39 -0700 Subject: [PATCH 0483/1047] Remove ThreadingContext entirely --- ...bstractTableDataSourceFindUsagesContext.cs | 19 ++++++++------- .../Entries/DocumentSpanEntry.cs | 4 ++-- .../StreamingFindUsagesPresenter.cs | 24 +++++++++---------- 3 files changed, 24 insertions(+), 23 deletions(-) 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/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/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs index ad9a525716c26..2df9c8431b446 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 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; @@ -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) From 9dc24a2e7133eae2b9bf1d686c1ecc454255b1bc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:08:12 -0700 Subject: [PATCH 0484/1047] Move off of ForegroundThreadAffinitizedObject --- .../VisualStudioChangeSignatureOptionsService.cs | 2 +- .../VisualStudioDesignerAttributeService.cs | 2 +- .../FindReferences/StreamingFindUsagesPresenter.cs | 4 ++-- .../InheritanceMargin/InheritanceGlyphManager.cs | 13 +++++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs b/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs index 687d1e6d2f242..337366293ca3e 100644 --- a/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs +++ b/src/VisualStudio/Core/Def/ChangeSignature/VisualStudioChangeSignatureOptionsService.cs @@ -16,7 +16,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature [ExportWorkspaceService(typeof(IChangeSignatureOptionsService), ServiceLayer.Host), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class VisualStudioChangeSignatureOptionsService( +internal sealed class VisualStudioChangeSignatureOptionsService( IClassificationFormatMapService classificationFormatMapService, ClassificationTypeMap classificationTypeMap, IThreadingContext threadingContext) : IChangeSignatureOptionsService diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index e616fad9473d5..8c4a5eccf23d5 100644 --- a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -29,7 +29,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute; [ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared] -internal class VisualStudioDesignerAttributeService : +internal sealed class VisualStudioDesignerAttributeService : IDesignerAttributeDiscoveryService.ICallback, IEventListener, IDisposable { private readonly VisualStudioWorkspaceImpl _workspace; diff --git a/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs index 2df9c8431b446..93ebde5aa7161 100644 --- a/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs +++ b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs @@ -34,7 +34,7 @@ namespace Microsoft.VisualStudio.LanguageServices.FindUsages; [Export(typeof(IStreamingFindUsagesPresenter)), Shared] -internal partial class StreamingFindUsagesPresenter : IStreamingFindUsagesPresenter +internal sealed partial class StreamingFindUsagesPresenter : IStreamingFindUsagesPresenter { public const string RoslynFindUsagesTableDataSourceIdentifier = nameof(RoslynFindUsagesTableDataSourceIdentifier); @@ -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 diff --git a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceGlyphManager.cs b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceGlyphManager.cs index 44226ca5fb53a..2dc543808a136 100644 --- a/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceGlyphManager.cs +++ b/src/VisualStudio/Core/Def/InheritanceMargin/InheritanceGlyphManager.cs @@ -10,6 +10,7 @@ using System.Windows.Media; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -26,7 +27,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMarg /// /// 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)) { From 8dd9179478551aea9b5a9f483b8d110c9238723a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:13:21 -0700 Subject: [PATCH 0485/1047] Move off of ForegroundThreadAffinitizedObject --- .../InheritanceMargin/InheritanceMarginViewMargin.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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; From 5095698e88c310030645f835e716801741f5e6c1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:14:15 -0700 Subject: [PATCH 0486/1047] Move off of ForegroundThreadAffinitizedObject --- .../Interop/CleanableWeakComHandleTable.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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; From 64530a2b31d9679c2593d028d4b3d91f5ad73f62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:15:01 -0700 Subject: [PATCH 0487/1047] Move off of ForegroundThreadAffinitizedObject --- .../ClassView/AbstractSyncClassViewCommandHandler.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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) From 42bb818ef88f17eb60893c1ecb6b532ce4f0f644 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:15:34 -0700 Subject: [PATCH 0488/1047] Move off of ForegroundThreadAffinitizedObject --- src/VisualStudio/Core/Def/Preview/AbstractChange.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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) From bfca6fb9e8dee8e9003dd85cfc7c615943a7163b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:16:19 -0700 Subject: [PATCH 0489/1047] Move off of ForegroundThreadAffinitizedObject --- src/VisualStudio/Core/Def/Preview/PreviewEngine.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs b/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs index d07c143def77c..f178442e75c7c 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; @@ -33,6 +33,7 @@ internal class PreviewEngine : ForegroundThreadAffinitizedObject, IVsPreviewChan private readonly Glyph _topLevelGlyph; private readonly string _helpString; private readonly string _description; + private readonly IThreadingContext _threadingContext; private readonly string _title; private readonly IComponentModel _componentModel; private readonly IVsImageService2 _imageService; @@ -60,10 +61,10 @@ public PreviewEngine( IComponentModel componentModel, IVsImageService2 imageService, bool showCheckBoxes = true) - : base(threadingContext) { _topLevelName = topLevelItemName; _topLevelGlyph = topLevelGlyph; + _threadingContext = threadingContext; _title = title ?? throw new ArgumentNullException(nameof(title)); _helpString = helpString ?? throw new ArgumentNullException(nameof(helpString)); _description = description ?? throw new ArgumentNullException(nameof(description)); @@ -226,7 +227,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(_threadingContext, EnsureTextViewIsInitialized(textView)); } private ITextView EnsureTextViewIsInitialized(object previewTextView) From 1c69d9165dae91974b795ca286183df38cfec7cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:16:55 -0700 Subject: [PATCH 0490/1047] Move off of ForegroundThreadAffinitizedObject --- .../Core/Def/Preview/PreviewService.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/VisualStudio/Core/Def/Preview/PreviewService.cs b/src/VisualStudio/Core/Def/Preview/PreviewService.cs index 84247ac889cf6..6101ae7b4394c 100644 --- a/src/VisualStudio/Core/Def/Preview/PreviewService.cs +++ b/src/VisualStudio/Core/Def/Preview/PreviewService.cs @@ -18,21 +18,14 @@ 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(IThreadingContext threadingContext, 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)); + private readonly IThreadingContext _threadingContext = threadingContext; public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => this; @@ -48,7 +41,7 @@ public Solution PreviewChanges( bool showCheckBoxes = true) { var engine = new PreviewEngine( - ThreadingContext, + _threadingContext, title, helpString, description, From 6d02a050c37e582cf3836fc8ee39e22a7ccaecd4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:18:19 -0700 Subject: [PATCH 0491/1047] Remove ThreadingContext entirely --- src/VisualStudio/Core/Def/Preview/PreviewEngine.cs | 9 +++------ src/VisualStudio/Core/Def/Preview/PreviewService.cs | 4 +--- src/VisualStudio/Core/Def/Preview/PreviewUpdater.cs | 5 ++--- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs b/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs index f178442e75c7c..cc956340ad856 100644 --- a/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs +++ b/src/VisualStudio/Core/Def/Preview/PreviewEngine.cs @@ -33,7 +33,6 @@ internal sealed class PreviewEngine : IVsPreviewChangesEngine private readonly Glyph _topLevelGlyph; private readonly string _helpString; private readonly string _description; - private readonly IThreadingContext _threadingContext; private readonly string _title; private readonly IComponentModel _componentModel; private readonly IVsImageService2 _imageService; @@ -44,13 +43,12 @@ internal sealed class PreviewEngine : IVsPreviewChangesEngine 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, @@ -64,7 +62,6 @@ public PreviewEngine( { _topLevelName = topLevelItemName; _topLevelGlyph = topLevelGlyph; - _threadingContext = threadingContext; _title = title ?? throw new ArgumentNullException(nameof(title)); _helpString = helpString ?? throw new ArgumentNullException(nameof(helpString)); _description = description ?? throw new ArgumentNullException(nameof(description)); @@ -227,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 6101ae7b4394c..eb83716ea0794 100644 --- a/src/VisualStudio/Core/Def/Preview/PreviewService.cs +++ b/src/VisualStudio/Core/Def/Preview/PreviewService.cs @@ -20,12 +20,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Preview; [ExportWorkspaceServiceFactory(typeof(IPreviewDialogService), ServiceLayer.Host), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class PreviewDialogService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) : IPreviewDialogService, IWorkspaceServiceFactory +internal sealed class PreviewDialogService(SVsServiceProvider serviceProvider) : IPreviewDialogService, IWorkspaceServiceFactory { 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)); - private readonly IThreadingContext _threadingContext = threadingContext; public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) => this; @@ -41,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); From f1daf08835ee8554c60ab0ca82d2234b01be3654 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:19:07 -0700 Subject: [PATCH 0492/1047] Remove ThreadingContext entirely --- .../Progression/GraphNavigatorExtension.cs | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) 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, From 267fe0cb6e841e302e7f625c24d130c70f254ea5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:20:06 -0700 Subject: [PATCH 0493/1047] Remove ThreadingContext entirely --- .../Core/Def/ProjectSystem/InvisibleEditor.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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!; From 05d6ec5c188038c597aa4781f05835adebdb1c37 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:20:45 -0700 Subject: [PATCH 0494/1047] Remove ThreadingContext entirely --- .../ProjectSystem/Legacy/AbstractLegacyProject.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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(); From b368b61815ae4b4793b25318dd000c0835d023f3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:21:44 -0700 Subject: [PATCH 0495/1047] Remove ThreadingContext entirely --- .../Legacy/SolutionEventsBatchScopeCreator.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) 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) { From 9eafa7e639ea25718ae0ff2b601671ae125f29fb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:25:01 -0700 Subject: [PATCH 0496/1047] Remove ThreadingContext entirely --- ...dioFrameworkAssemblyPathResolverFactory.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) 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) { From 4e2fe5654d73e837dd6c8d97aca4518d0fc738e5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:26:52 -0700 Subject: [PATCH 0497/1047] Remove ThreadingContext entirely --- .../ProjectSystem/OpenTextBufferProvider.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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); } From bb24dd9e8a997ec383afedd84537953880ac15d6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:27:31 -0700 Subject: [PATCH 0498/1047] Remove ThreadingContext entirely --- .../VisualStudioProjectManagementService.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs index 8f873e11e1c15..3c3a77866dd51 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs @@ -10,6 +10,7 @@ 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.ProjectManagement; @@ -20,18 +21,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) { From 2407edbc6995bf82f0fc9e7fb4d2082cd7eda580 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:29:03 -0700 Subject: [PATCH 0499/1047] Remove ThreadingContext entirely --- ...sualStudioWorkspaceImpl.OpenFileTracker.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index a2881ef13ee65..0fc833ad4bf81 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); @@ -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()) { From e7347571df69d20ebdb4793643ea626c0449d490 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:29:54 -0700 Subject: [PATCH 0500/1047] Remove ThreadingContext entirely --- .../Def/Shared/VisualStudioImageIdService.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) 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) { From 73a39d3fcb8953af01f36171b31c59de545d9d16 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:30:38 -0700 Subject: [PATCH 0501/1047] Remove ThreadingContext entirely --- .../Snippets/AbstractSnippetCommandHandler.cs | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs index dba6899125244..fbf2ffb7a7aec 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; + private 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(); + _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(); + _threadingContext.ThrowIfNotOnUIThread(); if (!AreSnippetsEnabled(args)) { @@ -114,7 +107,7 @@ public CommandState GetCommandState(AutomaticLineEnderCommandArgs args, Func _textManager.GetValueOrNullAsync(CancellationToken.None)); + var textManager = _threadingContext.JoinableTaskFactory.Run(() => _textManager.GetValueOrNullAsync(CancellationToken.None)); if (textManager == null) { expansionManager = null; From 47fbc56ba1a7c5bcd168cecee479db513ce561d7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:32:56 -0700 Subject: [PATCH 0502/1047] Remove ThreadingContext entirely --- .../Def/Snippets/SnippetExpansionClient.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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) { From 067b08df21c7148de0b8d65949f7f5f83e105538 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:35:54 -0700 Subject: [PATCH 0503/1047] Remove ThreadingContext entirely --- .../PackageInstallerServiceFactory.cs | 21 +++++++------- .../Snippets/AbstractSnippetCommandHandler.cs | 28 +++++++++---------- .../AbstractDelayStartedService.cs | 5 ++-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index c2102cb3edb89..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); @@ -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/Snippets/AbstractSnippetCommandHandler.cs b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs index fbf2ffb7a7aec..fdd5a124b81d0 100644 --- a/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Snippets/AbstractSnippetCommandHandler.cs @@ -37,7 +37,7 @@ internal abstract class AbstractSnippetCommandHandler( ICommandHandler, IChainedCommandHandler { - private readonly IThreadingContext _threadingContext = threadingContext; + protected readonly IThreadingContext ThreadingContext = threadingContext; private readonly EditorOptionsService _editorOptionsService = editorOptionsService; private readonly IVsService _textManager = textManager; @@ -54,7 +54,7 @@ protected virtual bool TryInvokeSnippetPickerOnQuestionMark(ITextView textView, public bool ExecuteCommand(TabKeyCommandArgs args, CommandExecutionContext context) { - _threadingContext.ThrowIfNotOnUIThread(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (AreSnippetsEnabledWithClient(args, out var snippetExpansionClient) && snippetExpansionClient.TryHandleTab()) { @@ -85,7 +85,7 @@ public bool ExecuteCommand(TabKeyCommandArgs args, CommandExecutionContext conte public CommandState GetCommandState(TabKeyCommandArgs args) { - _threadingContext.ThrowIfNotOnUIThread(); + this.ThreadingContext.ThrowIfNotOnUIThread(); if (!AreSnippetsEnabled(args)) { @@ -107,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/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; From c6b49268a39220b6fea2a44b436c6acb10c0c83e Mon Sep 17 00:00:00 2001 From: David Barbet Date: Mon, 15 Apr 2024 14:36:40 -0700 Subject: [PATCH 0504/1047] Do not report exception message in fault details --- .../Core/RenameTracking/RenameTrackingTaggerProvider.cs | 5 ++--- src/VisualStudio/Core/Def/Watson/FaultReporter.cs | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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/VisualStudio/Core/Def/Watson/FaultReporter.cs b/src/VisualStudio/Core/Def/Watson/FaultReporter.cs index ef3769c60fd94..0fddef0859234 100644 --- a/src/VisualStudio/Core/Def/Watson/FaultReporter.cs +++ b/src/VisualStudio/Core/Def/Watson/FaultReporter.cs @@ -252,8 +252,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() From 30cef8b0ee605a1daba13e9af78d2a6cab2181a8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:37:14 -0700 Subject: [PATCH 0505/1047] Remove ThreadingContext entirely --- .../Impl/Snippets/SnippetCommandHandler.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) 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) From b68a0ec7ef437600ad6b50bef52986a66d8b42fd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:38:27 -0700 Subject: [PATCH 0506/1047] Remove ThreadingContext entirely --- .../Def/Utilities/VsCodeWindowViewTracker.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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; From 3d814bcbc6fd816ac37145e457819ae7df017172 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:40:05 -0700 Subject: [PATCH 0507/1047] Move off of ForegroundThreadAffinitizedObject --- src/VisualStudio/Core/Def/Venus/ContainedDocument.cs | 5 +---- src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs | 1 - src/VisualStudio/Core/Test/Preview/PreviewChangesTests.vb | 5 ----- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs index 659163fe9c21b..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; diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs index 103ffe86eaa94..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, 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, From 66ec254fb889cc66f66e8c9c3b6b4a77512c22da Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:40:38 -0700 Subject: [PATCH 0508/1047] Move off of ForegroundThreadAffinitizedObject --- ...erviceFactory.WorkspaceGlobalUndoTransaction.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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); From 2fadff820e68a31f1598b7933dcd2496f24ab3bf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:41:33 -0700 Subject: [PATCH 0509/1047] Move off of ForegroundThreadAffinitizedObject --- .../Core/Def/Workspace/SourceGeneratedFileManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs index decf05d3f6d51..1bb5c963478d4 100644 --- a/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Workspace/SourceGeneratedFileManager.cs @@ -232,7 +232,7 @@ void IOpenTextBufferEventListener.OnRenameDocument(string newMoniker, string old { } - private sealed class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisposable + private sealed class OpenSourceGeneratedFile : IDisposable { private readonly SourceGeneratedFileManager _fileManager; private readonly ITextBuffer _textBuffer; @@ -267,8 +267,8 @@ private sealed class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject private InfoBarInfo? _infoToShow = null; public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, SourceGeneratedDocumentIdentity documentIdentity) - : base(fileManager._threadingContext, assertIsForeground: true) { + fileManager._threadingContext.ThrowIfNotOnUIThread(); _fileManager = fileManager; _textBuffer = textBuffer; _documentIdentity = documentIdentity; @@ -300,7 +300,7 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff private void DisconnectFromWorkspaceIfOpen() { - AssertIsForeground(); + _fileManager._threadingContext.ThrowIfNotOnUIThread(); if (this.Workspace.IsDocumentOpen(_documentIdentity.DocumentId)) { @@ -312,7 +312,7 @@ private void DisconnectFromWorkspaceIfOpen() public void Dispose() { - AssertIsForeground(); + _fileManager._threadingContext.ThrowIfNotOnUIThread(); this.Workspace.WorkspaceChanged -= OnWorkspaceChanged; @@ -370,7 +370,7 @@ public async ValueTask RefreshFileAsync(CancellationToken cancellationToken) } } - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _fileManager._threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); _infoToShow = infoToShow; @@ -476,7 +476,7 @@ internal async Task SetWindowFrameAsync(IVsWindowFrame windowFrame) return; _infoBar = new VisualStudioInfoBar( - this.ThreadingContext, _fileManager._vsInfoBarUIFactory, _fileManager._vsShell, _fileManager._listenerProvider, windowFrame); + _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); From b112676ea47c4143a57b2cccde08c5cf650cb7af Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:43:00 -0700 Subject: [PATCH 0510/1047] Move off of ForegroundThreadAffinitizedObject --- .../VisualStudioActiveDocumentTracker.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 71ccc2ccc14b3..83d642ae978f9 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -11,6 +11,7 @@ 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 +32,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 +53,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); @@ -92,7 +94,7 @@ public VisualStudioActiveDocumentTracker( /// 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 +124,7 @@ public VisualStudioActiveDocumentTracker( /// public ImmutableArray GetVisibleDocuments(Workspace workspace) { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); var visibleFramesSnapshot = _visibleFrames; @@ -143,7 +145,7 @@ public ImmutableArray GetVisibleDocuments(Workspace workspace) public void TrackNewActiveWindowFrame(IVsWindowFrame frame) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); Contract.ThrowIfNull(frame); @@ -168,7 +170,7 @@ public void TrackNewActiveWindowFrame(IVsWindowFrame frame) private void RemoveFrame(FrameListener frame) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (frame.Frame == _activeFrame) { @@ -185,7 +187,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 +229,7 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f { _documentTracker = service; - _documentTracker.AssertIsForeground(); + _documentTracker._threadingContext.ThrowIfNotOnUIThread(); this.Frame = frame; ((IVsWindowFrame2)frame).Advise(this, out _frameEventsCookie); @@ -293,7 +295,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))) { @@ -328,7 +330,7 @@ private void TryInitializeTextBuffer() private int Disconnect() { - _documentTracker.AssertIsForeground(); + _documentTracker._threadingContext.ThrowIfNotOnUIThread(); _documentTracker.RemoveFrame(this); if (TextBuffer != null) From 31da78fd518ebac6e8f14db3c3d22d3cb0d5c98b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:43:53 -0700 Subject: [PATCH 0511/1047] Move off of ForegroundThreadAffinitizedObject --- .../VisualStudioSymbolNavigationService.cs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs index c7f28425d598a..c703b64497112 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,8 @@ 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); + _threadingContext.ThrowIfNotOnUIThread(); var definitionItem = symbol.ToNonClassifiedDefinitionItem(project.Solution, includeHiddenLocations: true); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); @@ -230,7 +221,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 +268,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 +282,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) From f0107fa40affa28ff8e513e6e783df60e3df8289 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:44:48 -0700 Subject: [PATCH 0512/1047] Move off of ForegroundThreadAffinitizedObject --- .../Core/Def/Workspace/VisualStudioDocumentNavigationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs index cf4694301ceee..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; From 5387f73550cce825d73fafa86b931f133fdadd20 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:46:10 -0700 Subject: [PATCH 0513/1047] Move off of ForegroundThreadAffinitizedObject --- .../Utilities/ForegroundThreadAffinitizedObject.cs | 2 +- .../Core/Impl/CodeModel/ProjectCodeModelFactory.cs | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs b/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs index fd97c32227d96..983eb5ba6b2b9 100644 --- a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs +++ b/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs @@ -96,7 +96,7 @@ public Task InvokeBelowInputPriorityAsync(Action action, CancellationToken cance /// /// Returns true if any keyboard or mouse button input is pending on the message queue. /// - protected static bool IsInputPending() + public static bool IsInputPending() { // The code below invokes into user32.dll, which is not available in non-Windows. if (PlatformInformation.IsUnix) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index b6f80d5508f2d..9afbd60e97d0c 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; @@ -117,7 +110,7 @@ private async ValueTask ProcessNextDocumentBatchAsync( // Keep firing events for this doc, as long as we haven't exceeded the max amount // of waiting time, and there's no user input that should take precedence. - if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IsInputPending()) + if (stopwatch.Elapsed.Ticks > MaxTimeSlice || ForegroundThreadAffinitizedObject.IsInputPending()) { await this.Listener.Delay(delayBetweenProcessing, cancellationToken).ConfigureAwait(true); stopwatch = SharedStopwatch.StartNew(); From 206b193a3a1e1497c8952e7d79c2ffd45c215af0 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 15 Apr 2024 14:46:57 -0700 Subject: [PATCH 0514/1047] Don't load copilot assemblies if user's not logged in --- ...CSharpVisualStudioCopilotOptionsService.cs | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs index 17fb0d66489cc..54658af80bf81 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs @@ -11,30 +11,67 @@ 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 { - private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; + /// + /// Guid for UI Context that is set from Copilot when sign in related UI contexts have been 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. + /// + /// + + /// + /// 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 Copilot when the package is initialized + /// + private const string CopilotHasLoadedGuid = "871c3e1c-e58c-4ce9-b6a7-26600555739a"; + + /// + /// 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 static readonly UIContext s_gitHubAccountStatusIsCopilotEntitledUIContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusIsCopilotEntitled)); + private static readonly UIContext s_gitHubAccountStatusSignedInUIContext = UIContext.FromUIContextGuid(new Guid(GitHubAccountStatusSignedIn)); + private static readonly UIContext s_copilotHasLoadedUIContext = UIContext.FromUIContextGuid(new Guid(CopilotHasLoadedGuid)); + + private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; private const string CopilotCodeAnalysisOptionName = "EnableCSharpCodeAnalysis"; private const string CopilotRefineOptionName = "EnableCSharpRefineQuickActionSuggestion"; 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_gitHubAccountStatusSignedInUIContext.IsActive + && s_gitHubAccountStatusIsCopilotEntitledUIContext.IsActive; + [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpVisualStudioCopilotOptionsService( - IVsService settingsManagerService, - IThreadingContext threadingContext) + IVsService settingsManagerService, + IThreadingContext threadingContext) { _settingsManagerTask = settingsManagerService.GetValueAsync(threadingContext.DisposalToken); } public async Task IsCopilotOptionEnabledAsync(string optionName) { + if (IsGithubCopilotLoadedAndSignedIn is false) + 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 From 6d2e54c8337e6454782b125ab6146465adca17ab Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 15 Apr 2024 14:49:19 -0700 Subject: [PATCH 0515/1047] fix --- .../Impl/Options/CSharpVisualStudioCopilotOptionsService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs index 54658af80bf81..b7526d4677cb5 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs @@ -61,8 +61,8 @@ private static bool IsGithubCopilotLoadedAndSignedIn [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public CSharpVisualStudioCopilotOptionsService( - IVsService settingsManagerService, - IThreadingContext threadingContext) + IVsService settingsManagerService, + IThreadingContext threadingContext) { _settingsManagerTask = settingsManagerService.GetValueAsync(threadingContext.DisposalToken); } From 6b10899305947ae70503e997a385f661134b5eda Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 14:58:05 -0700 Subject: [PATCH 0516/1047] Move off of ForegroundThreadAffinitizedObject --- .../Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index a75997eee9798..8e063be3715bb 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(); + this. AssertIsForeground(); // 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))); From 0c048010796828eefce0ec2bbcec09ba110edae1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:19:47 -0700 Subject: [PATCH 0517/1047] Move off of ForegroundThreadAffinitizedObject --- .../Suggestions/SuggestedActionsSource.cs | 11 +++-- .../ForegroundThreadAffinitizedObject.cs | 48 +++++++++++-------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 885ccbc2995fd..be975381ed17e 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -28,7 +28,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 +37,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 +48,8 @@ public SuggestedActionsSource( ITextBuffer textBuffer, ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, IAsynchronousOperationListener listener) - : base(threadingContext) { + _threadingContext = threadingContext; GlobalOptions = globalOptions; _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; @@ -187,13 +188,13 @@ public Task HasSuggestedActionsAsync( // 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()) + if (_threadingContext.JoinableTaskContext.IsOnMainThread) { selection = TryGetCodeRefactoringSelection(state, range); } else { - await InvokeBelowInputPriorityAsync(() => + await _threadingContext.InvokeBelowInputPriorityAsync(() => { // Make sure we were not disposed between kicking off this work and getting to this point. using var state = _state.TryAddReference(); @@ -284,7 +285,7 @@ await InvokeBelowInputPriorityAsync(() => 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)) diff --git a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs b/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs index 983eb5ba6b2b9..b40d050500fb8 100644 --- a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs +++ b/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs @@ -70,8 +70,33 @@ public static void ThisCanBeCalledOnAnyThread() } public Task InvokeBelowInputPriorityAsync(Action action, CancellationToken cancellationToken = default) + => _threadingContext.InvokeBelowInputPriorityAsync(action, cancellationToken); + + /// + /// Returns true if any keyboard or mouse button input is pending on the message queue. + /// + public static bool IsInputPending() { - if (IsForeground() && !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; + } +} + +internal static class IThreadingContextExtensions +{ + public static Task InvokeBelowInputPriorityAsync(this IThreadingContext threadingContext, Action action, CancellationToken cancellationToken = default) + { + if (threadingContext.JoinableTaskContext.IsOnMainThread && !ForegroundThreadAffinitizedObject.IsInputPending()) { // Optimize to inline the action if we're already on the foreground thread // and there's no pending user input. @@ -84,7 +109,7 @@ public Task InvokeBelowInputPriorityAsync(Action action, CancellationToken cance return Task.Factory.SafeStartNewFromAsync( async () => { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); action(); }, @@ -92,23 +117,4 @@ public Task InvokeBelowInputPriorityAsync(Action action, CancellationToken cance TaskScheduler.Default); } } - - /// - /// Returns true if any keyboard or mouse button input is pending on the message queue. - /// - public static bool IsInputPending() - { - // The code below invokes into user32.dll, which is not available in non-Windows. - if (PlatformInformation.IsUnix) - { - return false; - } - - // The return value of GetQueueStatus is HIWORD:LOWORD. - // A non-zero value in HIWORD indicates some input message in the queue. - var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); - - const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); - return (result & InputMask) != 0; - } } From 04599a4d5a511bf4a8d7b529a541bcbda9061c53 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:20:16 -0700 Subject: [PATCH 0518/1047] Report diagnostic when lambda binding count exceeds threshold (#72823) --- .../Portable/Binder/Binder_Initializers.cs | 6 +- .../CSharp/Portable/Binder/Binder_Lambda.cs | 57 ++++++++++++++ .../Portable/BoundTree/UnboundLambda.cs | 1 + .../CSharp/Portable/CSharpResources.resx | 6 ++ .../Portable/Compiler/MethodCompiler.cs | 6 +- .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../CSharp/Portable/Errors/ErrorFacts.cs | 1 + .../Generated/ErrorFacts.Generated.cs | 1 + .../Portable/xlf/CSharpResources.cs.xlf | 12 ++- .../Portable/xlf/CSharpResources.de.xlf | 12 ++- .../Portable/xlf/CSharpResources.es.xlf | 12 ++- .../Portable/xlf/CSharpResources.fr.xlf | 12 ++- .../Portable/xlf/CSharpResources.it.xlf | 12 ++- .../Portable/xlf/CSharpResources.ja.xlf | 12 ++- .../Portable/xlf/CSharpResources.ko.xlf | 12 ++- .../Portable/xlf/CSharpResources.pl.xlf | 12 ++- .../Portable/xlf/CSharpResources.pt-BR.xlf | 12 ++- .../Portable/xlf/CSharpResources.ru.xlf | 12 ++- .../Portable/xlf/CSharpResources.tr.xlf | 12 ++- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 12 ++- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 12 ++- .../Semantics/OverloadResolutionPerfTests.cs | 78 ++++++++++++++++++- .../Semantics/OverloadResolutionTests.cs | 16 +++- .../Test/Syntax/Diagnostics/DiagnosticTest.cs | 1 + 24 files changed, 309 insertions(+), 21 deletions(-) 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_Lambda.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs index 9528274cc497e..3c59e4b103324 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,61 @@ 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) + { + 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/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/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index cd8a8755867fc..5d47bd91d0474 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6874,6 +6874,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. 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 15fee745c96a3..d04040b6dff27 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2308,6 +2308,7 @@ internal enum ErrorCode ERR_InterceptsLocationDuplicateFile = 9233, ERR_InterceptsLocationFileNotFound = 9234, ERR_InterceptsLocationDataInvalidPosition = 9235, + INF_TooManyBoundLambdas = 9236, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index e634deae8b73d..6ca5b85258d76 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2437,6 +2437,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_InterceptsLocationDuplicateFile: case ErrorCode.ERR_InterceptsLocationFileNotFound: case ErrorCode.ERR_InterceptsLocationDataInvalidPosition: + case ErrorCode.INF_TooManyBoundLambdas: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 64e748fa825be..62d7b4c68bec6 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -369,6 +369,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/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 9cb6c6666dea2..08aa73d9ec729 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -2467,6 +2467,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# @@ -13036,4 +13046,4 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 5350d1c59acab..7afd5f798ca1d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -2467,6 +2467,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. @@ -13036,4 +13046,4 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 2a449abc1f16c..333531f33ebcb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -2467,6 +2467,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 @@ -13036,4 +13046,4 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index e39c57737603f..5670acdf1bfd3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -2467,6 +2467,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 @@ -13036,4 +13046,4 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index d3378e3228a1f..5ae57a81ba5f6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -2467,6 +2467,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 @@ -13036,4 +13046,4 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 88b8066444024..660b612454dae 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -2467,6 +2467,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# 分散演算ではありません @@ -13036,4 +13046,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index b964b1a1d24fd..4dcbb46c1279f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -2467,6 +2467,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# 확산 작업이 아닙니다. @@ -13036,4 +13046,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index c7aceef3e3fc0..15e03b63e52c2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -2467,6 +2467,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# @@ -13036,4 +13046,4 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index eca4a0f9e327d..94452e162657f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -2467,6 +2467,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 @@ -13036,4 +13046,4 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index f00022bc0283a..695213d92f5f9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -2467,6 +2467,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# @@ -13037,4 +13047,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 3104df55006e0..721552b2359e5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -2467,6 +2467,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 @@ -13036,4 +13046,4 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 4a501f24b6625..d61012d7e3da5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -2467,6 +2467,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# 分布操作 @@ -13036,4 +13046,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 94d77f41046f6..01ddee39364a5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -2467,6 +2467,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 作業 @@ -13036,4 +13046,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - + \ No newline at end of file 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/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index 7e07986d321a0..587107287652c 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: From 19500874aa3c46f2b2d5b32ebf33a66508955c91 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:27:00 -0700 Subject: [PATCH 0519/1047] Move off of ForegroundThreadAffinitizedObject --- .../KeybindingResetDetector.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index 4eb03c294ac1b..d207a8cb1ebef 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -10,6 +10,7 @@ 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; @@ -44,7 +45,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 +61,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; @@ -94,8 +95,8 @@ public KeybindingResetDetector( IVsService vsShell, SVsServiceProvider serviceProvider, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext) { + _threadingContext = threadingContext; _globalOptions = globalOptions; _serviceProvider = serviceProvider; @@ -111,12 +112,12 @@ public Task InitializeAsync() return Task.CompletedTask; } - return InvokeBelowInputPriorityAsync(InitializeCore); + return _threadingContext.InvokeBelowInputPriorityAsync(InitializeCore); } private void InitializeCore() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (!_globalOptions.GetOption(KeybindingResetOptionsStorage.EnabledFeatureFlag)) { @@ -316,7 +317,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)) @@ -337,7 +338,7 @@ async Task EnsureOleCommandTargetAsync() return; } - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); _oleCommandTarget = _serviceProvider.GetServiceOnMainThread(); } @@ -345,7 +346,7 @@ async Task EnsureOleCommandTargetAsync() private void RestoreVsKeybindings() { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); _uiShell ??= _serviceProvider.GetServiceOnMainThread(); @@ -362,7 +363,7 @@ private void RestoreVsKeybindings() private void OpenExtensionsHyperlink() { - ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); VisualStudioNavigateToLinkService.StartBrowser(KeybindingsFwLink); @@ -377,19 +378,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; } @@ -397,7 +399,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 @@ -410,7 +413,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 @@ -426,7 +429,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) { From ce85ffbc5ec791b82e22a89dd206c55d818c0b01 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:27:30 -0700 Subject: [PATCH 0520/1047] Move off of ForegroundThreadAffinitizedObject --- .../Suggestions/SuggestedActionsSource_Async.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index 8e063be3715bb..1a78c33ae59e2 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) { - this. 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,7 +69,7 @@ private async Task GetSuggestedActionsWorkerAsync( ArrayBuilder completedCollectors, CancellationToken cancellationToken) { - AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); using var state = SourceState.TryAddReference(); if (state is null) return; @@ -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() From 5393a7b6d0df1faf1d07a59fdfd6eea51a4dcfa3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:29:21 -0700 Subject: [PATCH 0521/1047] Move off of ForegroundThreadAffinitizedObject --- .../KeybindingResetDetector.cs | 3 --- .../MiscellaneousFilesWorkspace.cs | 19 +++++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index d207a8cb1ebef..f0eab888879e2 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -13,13 +13,10 @@ 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; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs index b8fe5b8a55ddf..ace82412c8ab2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -13,6 +13,7 @@ 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; @@ -30,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; @@ -50,8 +52,6 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IOpenText private readonly ImmutableArray _metadataReferences; - private readonly ForegroundThreadAffinitizedObject _foregroundThreadAffinitization; - private IVsTextManager _textManager; [ImportingConstructor] @@ -64,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; @@ -135,7 +134,7 @@ private IEnumerable CreateMetadataReferences() private void TrackOpenedDocument(string moniker, ITextBuffer textBuffer) { - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); var languageInformation = TryGetLanguageInformation(moniker); if (languageInformation == null) @@ -165,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; @@ -230,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; @@ -258,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())) { @@ -293,7 +292,7 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) private void DetachFromDocument(string moniker) { - _foregroundThreadAffinitization.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_fileTrackingMetadataAsSourceService.TryRemoveDocumentFromWorkspace(moniker)) { return; From dfc3ffd6d1f27801c034b51ee60d7310dda58e5d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:33:18 -0700 Subject: [PATCH 0522/1047] Move off of ForegroundThreadAffinitizedObject --- .../VisualStudioWorkspaceImpl.cs | 20 +++++++------------ ...ualStudioWorkspaceImpl_SourceGenerators.cs | 3 ++- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index 6eeae1a960122..5d5366d40dd6b 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -16,6 +16,7 @@ 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; @@ -75,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; @@ -121,8 +117,6 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro _projectionBufferFactoryService = exportProvider.GetExportedValue(); _projectCodeModelFactory = exportProvider.GetExport(); - _foregroundObject = new ForegroundThreadAffinitizedObject(_threadingContext); - _textBufferFactoryService.TextBufferCreated += AddTextBufferCloneServiceToBuffer; _projectionBufferFactoryService.ProjectionBufferCreated += AddTextBufferCloneServiceToBuffer; @@ -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)) { @@ -1085,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); } @@ -1349,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. @@ -1473,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 index 5c28ec422cc47..d11f95df75d4a 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl_SourceGenerators.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl_SourceGenerators.cs @@ -6,6 +6,7 @@ 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; @@ -23,7 +24,7 @@ internal abstract partial class VisualStudioWorkspaceImpl public void SubscribeToSourceGeneratorImpactingEvents() { - _foregroundObject.AssertIsForeground(); + _threadingContext.ThrowIfNotOnUIThread(); if (_isSubscribedToSourceGeneratorImpactingEvents) return; From dcf031ef9cdf07ff11b0d0823afe0c9089f10d6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:35:09 -0700 Subject: [PATCH 0523/1047] Remove type --- .../ForegroundThreadAffinitizedObject.cs | 120 ------------------ .../Utilities/IThreadingContextExtensions.cs | 57 +++++++++ ...sualStudioWorkspaceImpl.OpenFileTracker.cs | 2 +- .../Impl/CodeModel/ProjectCodeModelFactory.cs | 2 +- 4 files changed, 59 insertions(+), 122 deletions(-) delete mode 100644 src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs create mode 100644 src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs diff --git a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs b/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs deleted file mode 100644 index b40d050500fb8..0000000000000 --- a/src/EditorFeatures/Core/Shared/Utilities/ForegroundThreadAffinitizedObject.cs +++ /dev/null @@ -1,120 +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) - => _threadingContext.InvokeBelowInputPriorityAsync(action, cancellationToken); - - /// - /// Returns true if any keyboard or mouse button input is pending on the message queue. - /// - public static bool IsInputPending() - { - // The code below invokes into user32.dll, which is not available in non-Windows. - if (PlatformInformation.IsUnix) - { - return false; - } - - // The return value of GetQueueStatus is HIWORD:LOWORD. - // A non-zero value in HIWORD indicates some input message in the queue. - var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); - - const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); - return (result & InputMask) != 0; - } -} - -internal static class IThreadingContextExtensions -{ - public static Task InvokeBelowInputPriorityAsync(this IThreadingContext threadingContext, Action action, CancellationToken cancellationToken = default) - { - if (threadingContext.JoinableTaskContext.IsOnMainThread && !ForegroundThreadAffinitizedObject.IsInputPending()) - { - // Optimize to inline the action if we're already on the foreground thread - // and there's no pending user input. - action(); - - return Task.CompletedTask; - } - else - { - return Task.Factory.SafeStartNewFromAsync( - async () => - { - await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - action(); - }, - cancellationToken, - TaskScheduler.Default); - } - } -} diff --git a/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs b/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs new file mode 100644 index 0000000000000..069816e08b4e7 --- /dev/null +++ b/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; + +internal static class IThreadingContextExtensions +{ + /// + /// Returns true if any keyboard or mouse button input is pending on the message queue. + /// + public static bool IsInputPending() + { + // The code below invokes into user32.dll, which is not available in non-Windows. + if (PlatformInformation.IsUnix) + { + return false; + } + + // The return value of GetQueueStatus is HIWORD:LOWORD. + // A non-zero value in HIWORD indicates some input message in the queue. + var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); + + const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); + return (result & InputMask) != 0; + } + + public static Task InvokeBelowInputPriorityAsync(this IThreadingContext threadingContext, Action action, CancellationToken cancellationToken = default) + { + if (threadingContext.JoinableTaskContext.IsOnMainThread && !IsInputPending()) + { + // Optimize to inline the action if we're already on the foreground thread + // and there's no pending user input. + action(); + + return Task.CompletedTask; + } + else + { + return Task.Factory.SafeStartNewFromAsync( + async () => + { + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + action(); + }, + cancellationToken, + TaskScheduler.Default); + } + } +} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index 0fc833ad4bf81..a011645396ef9 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -349,7 +349,7 @@ private void TryClosingDocumentsForMoniker(string moniker) public Task CheckForAddedFileBeingOpenMaybeAsync(bool useAsync, ImmutableArray newFileNames) { - ForegroundThreadAffinitizedObject.ThisCanBeCalledOnAnyThread(); + // ThisCanBeCalledOnAnyThread(); return _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => { diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 9afbd60e97d0c..75d7d1abb1dd7 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -110,7 +110,7 @@ private async ValueTask ProcessNextDocumentBatchAsync( // Keep firing events for this doc, as long as we haven't exceeded the max amount // of waiting time, and there's no user input that should take precedence. - if (stopwatch.Elapsed.Ticks > MaxTimeSlice || ForegroundThreadAffinitizedObject.IsInputPending()) + if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IThreadingContextExtensions.IsInputPending()) { await this.Listener.Delay(delayBetweenProcessing, cancellationToken).ConfigureAwait(true); stopwatch = SharedStopwatch.StartNew(); From ad9fdb6238dabb8735a54f05dd3074ed33ce81a4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 15 Apr 2024 15:42:56 -0700 Subject: [PATCH 0524/1047] Remove code --- .../DesignerAttribute/VisualStudioDesignerAttributeService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index 8c4a5eccf23d5..aeddff70e487b 100644 --- a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -180,7 +180,6 @@ private async Task NotifyLegacyProjectSystemAsync( { // legacy project system can only be talked to on the UI thread. await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - _threadingContext.ThrowIfNotOnUIThread(); var designerService = _legacyDesignerService ??= (IVSMDDesignerService)_serviceProvider.GetService(typeof(SVSMDDesignerService)); if (designerService == null) @@ -278,7 +277,6 @@ private static async Task NotifyCpsProjectSystemAsync( if (!_cpsProjects.TryGetValue(projectId, out var updateService)) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - _threadingContext.ThrowIfNotOnUIThread(); updateService = ComputeUpdateService(); _cpsProjects.TryAdd(projectId, updateService); From c9a79cf996c7bc5ef02eb2e385e5cc795a50e03d Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 15 Apr 2024 15:53:11 -0700 Subject: [PATCH 0525/1047] fix --- .../CSharpVisualStudioCopilotOptionsService.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs index b7526d4677cb5..c816171d4a007 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs @@ -18,31 +18,35 @@ 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 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. /// /// + /// This UI context should always be set to false at the beginning of other UI context operations and then to true after operations finish. + /// This ensures that the UI contexts are only read when they are finalized and not after each UI context is set. + /// + 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 Copilot when the package is initialized - /// - private const string CopilotHasLoadedGuid = "871c3e1c-e58c-4ce9-b6a7-26600555739a"; - /// /// 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 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 static readonly UIContext s_copilotHasLoadedUIContext = UIContext.FromUIContextGuid(new Guid(CopilotHasLoadedGuid)); private const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; private const string CopilotCodeAnalysisOptionName = "EnableCSharpCodeAnalysis"; @@ -55,6 +59,7 @@ internal sealed class CSharpVisualStudioCopilotOptionsService : ICopilotOptionsS /// private static bool IsGithubCopilotLoadedAndSignedIn => s_copilotHasLoadedUIContext.IsActive + && s_gitHubAccountStatusDeterminedContext.IsActive && s_gitHubAccountStatusSignedInUIContext.IsActive && s_gitHubAccountStatusIsCopilotEntitledUIContext.IsActive; From f9d6aea413194e8ecb7f74288666323c5b254379 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Mon, 15 Apr 2024 15:43:24 -0700 Subject: [PATCH 0526/1047] Fix flaky lsp mutating request tests --- .../AbstractLanguageServerProtocolTests.cs | 8 ++++++++ .../DocumentChanges/DocumentChangesTests.cs | 9 ++++++--- .../ProtocolUnitTests/LanguageServerTargetTests.cs | 14 +++----------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 934597d8b7067..721f113d2db24 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -715,6 +715,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/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/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 { From 5b5a4981ce312f3d40e5ce0755ad26674dabd3f2 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Mon, 15 Apr 2024 16:28:51 -0700 Subject: [PATCH 0527/1047] Use different description property as old one is being suppressed --- src/VisualStudio/Core/Def/Watson/FaultReporter.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Watson/FaultReporter.cs b/src/VisualStudio/Core/Def/Watson/FaultReporter.cs index 0fddef0859234..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); From 1498459a583f323d6e5dbafebc4ff2c1e934c2d7 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 15 Apr 2024 16:33:21 -0700 Subject: [PATCH 0528/1047] address review comments --- .../CSharpVisualStudioCopilotOptionsService.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs index c816171d4a007..5012e6c7c4acb 100644 --- a/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs +++ b/src/VisualStudio/CSharp/Impl/Options/CSharpVisualStudioCopilotOptionsService.cs @@ -24,13 +24,9 @@ internal sealed class CSharpVisualStudioCopilotOptionsService : ICopilotOptionsS 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 properly. Used to determine when UI context status is final + /// 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. /// - /// - /// This UI context should always be set to false at the beginning of other UI context operations and then to true after operations finish. - /// This ensures that the UI contexts are only read when they are finalized and not after each UI context is set. - /// private const string GitHubAccountStatusDetermined = "3049be7e-71ee-4045-a510-f8ee1a967723"; /// @@ -43,15 +39,15 @@ internal sealed class CSharpVisualStudioCopilotOptionsService : ICopilotOptionsS /// 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 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 const string CopilotOptionNamePrefix = "Microsoft.VisualStudio.Conversations"; - private const string CopilotCodeAnalysisOptionName = "EnableCSharpCodeAnalysis"; - private const string CopilotRefineOptionName = "EnableCSharpRefineQuickActionSuggestion"; - private readonly Task _settingsManagerTask; /// @@ -74,7 +70,7 @@ public CSharpVisualStudioCopilotOptionsService( public async Task IsCopilotOptionEnabledAsync(string optionName) { - if (IsGithubCopilotLoadedAndSignedIn is false) + if (!IsGithubCopilotLoadedAndSignedIn) return false; var settingManager = await _settingsManagerTask.ConfigureAwait(false); From c91617f99586d5478a247393c21c8102adab0154 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 15 Apr 2024 17:40:15 -0700 Subject: [PATCH 0529/1047] Only trigger corehost integration tests on supported release branches We have removed option to use desktop OOP in 17.11 --- azure-pipelines-integration-corehost.yml | 32 ++++++++---------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/azure-pipelines-integration-corehost.yml b/azure-pipelines-integration-corehost.yml index fa6f2d32a9959..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/* From 8a59045624305c2f943be6786306e7de2221549d Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:49:48 +0000 Subject: [PATCH 0530/1047] Update dependencies from https://github.com/dotnet/source-build-externals build 20240415.1 (#73045) [main] Update dependencies from dotnet/source-build-externals --- eng/Version.Details.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 63933ff92bed1..97d63d81605e0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,9 +2,9 @@ - + https://github.com/dotnet/source-build-externals - 83566118e44922c30d146654d42c7c3745cc119d + f4e750201aaf6bad67391a52e8138bbd7abe3019 From 3292c2a43eabd1476a6f98e9981611e55aaba3c8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 10:47:30 -0700 Subject: [PATCH 0531/1047] Simplify code action threading code --- .../Suggestions/SuggestedActionsSource.cs | 90 +++++-------------- 1 file changed, 20 insertions(+), 70 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index be975381ed17e..a6ec9d59d538c 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -146,68 +146,6 @@ public Task HasSuggestedActionsAsync( throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); } - private async Task GetSpanAsync(ReferenceCountedDisposable state, SnapshotSpan range, CancellationToken cancellationToken) - { - // First, ensure that the snapshot we're being asked about is for an actual - // roslyn document. This can fail, for example, in projection scenarios where - // we are called with a range snapshot that refers to the projection buffer - // and not the actual roslyn code that is being projected into it. - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - { - return null; - } - - // Also make sure the range is from the same buffer that this source was created for - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - - // Next, before we do any async work, acquire the user's selection, directly grabbing - // it from the UI thread if that's what we're on. That way we don't have any reentrancy - // blocking concerns if VS wants to block on this call (for example, if the user - // explicitly invokes the 'show smart tag' command). - // - // This work must happen on the UI thread as it needs to access the _textView's mutable - // state. - // - // Note: we may be called in one of two VS scenarios: - // 1) User has moved caret to a new line. In this case VS will call into us in the - // bg to see if we have any suggested actions for this line. In order to figure - // this out, we need to see what selection the user has (for refactorings), which - // necessitates going back to the fg. - // - // 2) User moves to a line and immediately hits ctrl-dot. In this case, on the UI - // thread VS will kick us off and then immediately block to get the results so - // that they can expand the light-bulb. In this case we cannot do BG work first, - // then call back into the UI thread to try to get the user selection. This will - // deadlock as the UI thread is blocked on us. - // - // There are two solution to '2'. Either introduce reentrancy (which we really don't - // like to do), or just ensure that we acquire and get the users selection up front. - // This means that when we're called from the UI thread, we never try to go back to the - // UI thread. - TextSpan? selection = null; - if (_threadingContext.JoinableTaskContext.IsOnMainThread) - { - selection = TryGetCodeRefactoringSelection(state, range); - } - else - { - await _threadingContext.InvokeBelowInputPriorityAsync(() => - { - // Make sure we were not disposed between kicking off this work and getting to this point. - using var state = _state.TryAddReference(); - if (state is null) - return; - - selection = TryGetCodeRefactoringSelection(state, range); - }, cancellationToken).ConfigureAwait(false); - } - - return selection; - } - private static async Task GetFixLevelAsync( ReferenceCountedDisposable state, TextDocument document, @@ -215,6 +153,8 @@ await _threadingContext.InvokeBelowInputPriorityAsync(() => CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); var lowPriorityAnalyzers = new ConcurrentSet(); foreach (var order in Orderings) @@ -258,6 +198,9 @@ await _threadingContext.InvokeBelowInputPriorityAsync(() => CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); + using var state = _state.TryAddReference(); if (state is null) return null; @@ -318,6 +261,11 @@ private void OnTextViewClosed(object sender, EventArgs e) if (state is null) return null; + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); + var workspace = state.Target.Workspace; if (workspace == null) return null; @@ -338,16 +286,18 @@ private void OnTextViewClosed(object sender, EventArgs e) using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var linkedToken = linkedTokenSource.Token; - var errorTask = Task.Run(() => GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken), linkedToken); + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken); - var selection = await GetSpanAsync(state, range, linkedToken).ConfigureAwait(false); + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + var selection = TryGetCodeRefactoringSelection(state, range); + await Task.Yield().ConfigureAwait(false); - var refactoringTask = SpecializedTasks.Null(); - if (selection != null) - { - refactoringTask = Task.Run( - () => TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken), linkedToken); - } + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken) + : SpecializedTasks.Null(); // If we happen to get the result of the error task before the refactoring task, // and that result is non-null, we can just cancel the refactoring task. From a5aaa796a08e2a1db5f74f387805d6f9c08d0e0c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 10:48:14 -0700 Subject: [PATCH 0532/1047] remove unnecessary call --- .../Core/Def/Workspace/VisualStudioSymbolNavigationService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs index c703b64497112..8013cdac4f854 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs @@ -180,7 +180,6 @@ await navigationService.TryNavigateToSpanAsync( public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - _threadingContext.ThrowIfNotOnUIThread(); var definitionItem = symbol.ToNonClassifiedDefinitionItem(project.Solution, includeHiddenLocations: true); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); From ea01eefb1bd37016074ff8a1b14b92bdd2955d08 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 10:53:13 -0700 Subject: [PATCH 0533/1047] Move to normal switch call. --- .../Suggestions/SuggestedActionsSource.cs | 2 +- .../Utilities/IThreadingContextExtensions.cs | 57 ------------------- .../KeybindingResetDetector.cs | 9 ++- src/VisualStudio/Core/Def/RoslynPackage.cs | 2 +- .../Impl/CodeModel/ProjectCodeModelFactory.cs | 17 +++++- 5 files changed, 22 insertions(+), 65 deletions(-) delete mode 100644 src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index a6ec9d59d538c..014764f072a0e 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -290,7 +290,7 @@ private void OnTextViewClosed(object sender, EventArgs e) var errorTask = GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken); // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); var selection = TryGetCodeRefactoringSelection(state, range); await Task.Yield().ConfigureAwait(false); diff --git a/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs b/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs deleted file mode 100644 index 069816e08b4e7..0000000000000 --- a/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; - -internal static class IThreadingContextExtensions -{ - /// - /// Returns true if any keyboard or mouse button input is pending on the message queue. - /// - public static bool IsInputPending() - { - // The code below invokes into user32.dll, which is not available in non-Windows. - if (PlatformInformation.IsUnix) - { - return false; - } - - // The return value of GetQueueStatus is HIWORD:LOWORD. - // A non-zero value in HIWORD indicates some input message in the queue. - var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); - - const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); - return (result & InputMask) != 0; - } - - public static Task InvokeBelowInputPriorityAsync(this IThreadingContext threadingContext, Action action, CancellationToken cancellationToken = default) - { - if (threadingContext.JoinableTaskContext.IsOnMainThread && !IsInputPending()) - { - // Optimize to inline the action if we're already on the foreground thread - // and there's no pending user input. - action(); - - return Task.CompletedTask; - } - else - { - return Task.Factory.SafeStartNewFromAsync( - async () => - { - await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - action(); - }, - cancellationToken, - TaskScheduler.Default); - } - } -} diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index f0eab888879e2..0711327e2ebf1 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -101,15 +101,14 @@ public KeybindingResetDetector( _infoBar = new VisualStudioInfoBar(threadingContext, vsInfoBarUIFactory, vsShell, listenerProvider, windowFrame: null); } - public Task InitializeAsync() + public async Task InitializeAsync(CancellationToken cancellationToken) { // Immediately bail if the user has asked to never see this bar again. if (_globalOptions.GetOption(KeybindingResetOptionsStorage.NeverShowAgain)) - { - return Task.CompletedTask; - } + return; - return _threadingContext.InvokeBelowInputPriorityAsync(InitializeCore); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + InitializeCore(); } private void InitializeCore() diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 79a33a370c61e..93730649e413e 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -273,7 +273,7 @@ private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationT await LoadStackTraceExplorerMenusAsync(cancellationToken).ConfigureAwait(true); // Initialize keybinding reset detector - await ComponentModel.DefaultExportProvider.GetExportedValue().InitializeAsync().ConfigureAwait(true); + await ComponentModel.DefaultExportProvider.GetExportedValue().InitializeAsync(cancellationToken).ConfigureAwait(true); } private async Task LoadStackTraceExplorerMenusAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 75d7d1abb1dd7..3c1acd9e7c9aa 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -110,7 +110,7 @@ private async ValueTask ProcessNextDocumentBatchAsync( // Keep firing events for this doc, as long as we haven't exceeded the max amount // of waiting time, and there's no user input that should take precedence. - if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IThreadingContextExtensions.IsInputPending()) + if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IsInputPending()) { await this.Listener.Delay(delayBetweenProcessing, cancellationToken).ConfigureAwait(true); stopwatch = SharedStopwatch.StartNew(); @@ -140,6 +140,21 @@ void FireEventsForDocument(DocumentId documentId) codeModel.FireEvents(); return; } + + // Returns true if any keyboard or mouse button input is pending on the message queue. + static bool IsInputPending() + { + // The code below invokes into user32.dll, which is not available in non-Windows. + if (PlatformInformation.IsUnix) + return false; + + // The return value of GetQueueStatus is HIWORD:LOWORD. + // A non-zero value in HIWORD indicates some input message in the queue. + var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); + + const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); + return (result & InputMask) != 0; + } } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) From e4d4c1d0088fdfaf88297f7250966b4a5978109a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 11:54:54 -0700 Subject: [PATCH 0534/1047] Pass in state --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 014764f072a0e..58aee5d6f22c9 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -193,6 +193,7 @@ public Task HasSuggestedActionsAsync( } private async Task TryGetRefactoringSuggestedActionCategoryAsync( + ReferenceCountedDisposable state, TextDocument document, TextSpan? selection, CodeActionOptionsProvider fallbackOptions, @@ -201,10 +202,6 @@ public Task HasSuggestedActionsAsync( // Ensure we yield the thread that called into us, allowing it to continue onwards. await Task.Yield().ConfigureAwait(false); - using var state = _state.TryAddReference(); - if (state is null) - return null; - if (!selection.HasValue) { // this is here to fail test and see why it is failed. @@ -296,7 +293,7 @@ private void OnTextViewClosed(object sender, EventArgs e) // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. var refactoringTask = selection != null - ? TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken) + ? TryGetRefactoringSuggestedActionCategoryAsync(state, document, selection, fallbackOptions, linkedToken) : SpecializedTasks.Null(); // If we happen to get the result of the error task before the refactoring task, From 388ac6028c9eb9d2cc0dc53f2427555e4299d072 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:02:40 -0700 Subject: [PATCH 0535/1047] Refactor --- .../Suggestions/SuggestedActionsSource.cs | 373 ++++++++---------- .../SuggestedActionsSource_Async.cs | 2 +- 2 files changed, 175 insertions(+), 200 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 58aee5d6f22c9..aa6c0676f7b95 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -24,134 +24,168 @@ using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; -namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions +namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; + +internal partial class SuggestedActionsSourceProvider { - internal partial class SuggestedActionsSourceProvider + private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 { - private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 - { - private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; + private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - private readonly ReferenceCountedDisposable _state; - private readonly IAsynchronousOperationListener _listener; + private readonly ReferenceCountedDisposable _state; + private readonly IAsynchronousOperationListener _listener; - public event EventHandler? SuggestedActionsChanged { add { } remove { } } + public event EventHandler? SuggestedActionsChanged { add { } remove { } } - private readonly IThreadingContext _threadingContext; - public readonly IGlobalOptionService GlobalOptions; + private readonly IThreadingContext _threadingContext; + public readonly IGlobalOptionService GlobalOptions; - public SuggestedActionsSource( - IThreadingContext threadingContext, - IGlobalOptionService globalOptions, - SuggestedActionsSourceProvider owner, - ITextView textView, - ITextBuffer textBuffer, - ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, - IAsynchronousOperationListener listener) - { - _threadingContext = threadingContext; - GlobalOptions = globalOptions; + public SuggestedActionsSource( + IThreadingContext threadingContext, + IGlobalOptionService globalOptions, + SuggestedActionsSourceProvider owner, + ITextView textView, + ITextBuffer textBuffer, + ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, + IAsynchronousOperationListener listener) + { + _threadingContext = threadingContext; + GlobalOptions = globalOptions; + + _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; + _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + + _state.Target.TextView.Closed += OnTextViewClosed; + _listener = listener; + } + + public void Dispose() + => _state.Dispose(); - _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; - _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = default; - _state.Target.TextView.Closed += OnTextViewClosed; - _listener = listener; + using var state = _state.TryAddReference(); + if (state is null) + { + return false; } - public void Dispose() + var workspace = state.Target.Workspace; + if (workspace == null) { - _state.Dispose(); + return false; } - private ReferenceCountedDisposable SourceState => _state; + var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); + if (documentId == null) + { + return false; + } - public bool TryGetTelemetryId(out Guid telemetryId) + var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); + if (project == null) { - telemetryId = default; + return false; + } - using var state = _state.TryAddReference(); - if (state is null) - { + switch (project.Language) + { + case LanguageNames.CSharp: + telemetryId = s_CSharpSourceGuid; + return true; + case LanguageNames.VisualBasic: + telemetryId = s_visualBasicSourceGuid; + return true; + case "Xaml": + telemetryId = s_xamlSourceGuid; + return true; + default: return false; - } + } + } - var workspace = state.Target.Workspace; - if (workspace == null) - { - return false; - } + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + => null; + + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + IUIThreadOperationContext operationContext) + => null; + + public Task HasSuggestedActionsAsync( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + { + // We implement GetSuggestedActionCategoriesAsync so this should not be called + throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); + } - var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); - if (documentId == null) - { - return false; - } + private void OnTextViewClosed(object sender, EventArgs e) + => Dispose(); - var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); - if (project == null) - { - return false; - } + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + using var state = _state.TryAddReference(); + if (state is null) + return null; - switch (project.Language) - { - case LanguageNames.CSharp: - telemetryId = s_CSharpSourceGuid; - return true; - case LanguageNames.VisualBasic: - telemetryId = s_visualBasicSourceGuid; - return true; - case "Xaml": - telemetryId = s_xamlSourceGuid; - return true; - default: - return false; - } - } + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - => null; + var workspace = state.Target.Workspace; + if (workspace == null) + return null; - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - IUIThreadOperationContext operationContext) - => null; + // never show light bulb if solution is not fully loaded yet + if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) + return null; - private static string GetFixCategory(DiagnosticSeverity severity) - { - switch (severity) - { - case DiagnosticSeverity.Hidden: - case DiagnosticSeverity.Info: - case DiagnosticSeverity.Warning: - return PredefinedSuggestedActionCategoryNames.CodeFix; - case DiagnosticSeverity.Error: - return PredefinedSuggestedActionCategoryNames.ErrorFix; - default: - throw ExceptionUtilities.Unreachable(); - } - } + cancellationToken.ThrowIfCancellationRequested(); - public Task HasSuggestedActionsAsync( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - { - // We implement GetSuggestedActionCategoriesAsync so this should not be called - throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); - } + using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); + var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); + if (document == null) + return null; + + var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); + + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + // Assign over cancellation token so no one accidentally uses the wrong token. + cancellationToken = linkedTokenSource.Token; + + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(); - private static async Task GetFixLevelAsync( - ReferenceCountedDisposable state, - TextDocument document, - SnapshotSpan range, - CodeActionOptionsProvider fallbackOptions, - CancellationToken cancellationToken) + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + + var selection = TryGetCodeRefactoringSelection(state, range); + await Task.Yield().ConfigureAwait(false); + + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(selection) + : SpecializedTasks.Null(); + + // If we happen to get the result of the error task before the refactoring task, + // and that result is non-null, we can just cancel the refactoring task. + var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); + linkedTokenSource.Cancel(); + + return result == null + ? null + : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); + + async Task GetFixLevelAsync() { // Ensure we yield the thread that called into us, allowing it to continue onwards. await Task.Yield().ConfigureAwait(false); @@ -163,41 +197,42 @@ public Task HasSuggestedActionsAsync( Contract.ThrowIfNull(priority); var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); - var result = await GetFixLevelAsync(priorityProvider).ConfigureAwait(false); + var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); if (result != null) return result; } return null; + } - async Task GetFixLevelAsync(ICodeActionRequestPriorityProvider priorityProvider) + async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) + { + if (state.Target.Owner._codeFixService != null && + state.Target.SubjectBuffer.SupportsCodeFixes()) { - if (state.Target.Owner._codeFixService != null && - state.Target.SubjectBuffer.SupportsCodeFixes()) - { - var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( - document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); + var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( + document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); - if (result.HasFix) + if (result.HasFix) + { + Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); + return result.CodeFixCollection.FirstDiagnostic.Severity switch { - Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); - return GetFixCategory(result.CodeFixCollection.FirstDiagnostic.Severity); - } - if (!result.UpToDate) - return null; + DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, + DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, + _ => throw ExceptionUtilities.Unreachable(), + }; } - return null; + if (!result.UpToDate) + return null; } + + return null; } - private async Task TryGetRefactoringSuggestedActionCategoryAsync( - ReferenceCountedDisposable state, - TextDocument document, - TextSpan? selection, - CodeActionOptionsProvider fallbackOptions, - CancellationToken cancellationToken) + async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { // Ensure we yield the thread that called into us, allowing it to continue onwards. await Task.Yield().ConfigureAwait(false); @@ -222,89 +257,29 @@ public Task HasSuggestedActionsAsync( return null; } + } - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) - { - _threadingContext.ThrowIfNotOnUIThread(); - - var selectedSpans = state.Target.TextView.Selection.SelectedSpans - .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) - .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) - .ToList(); - - // We only support refactorings when there is a single selection in the document. - if (selectedSpans.Count != 1) - { - return null; - } - - var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - - // We only support refactorings when selected span intersects with the span that the light bulb is asking for. - if (!translatedSpan.IntersectsWith(range)) - { - return null; - } - - return translatedSpan.Span.ToTextSpan(); - } - - private void OnTextViewClosed(object sender, EventArgs e) - => Dispose(); - - public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) - { - using var state = _state.TryAddReference(); - if (state is null) - return null; - - // Make sure the range is from the same buffer that this source was created for. - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - - var workspace = state.Target.Workspace; - if (workspace == null) - return null; - - // never show light bulb if solution is not fully loaded yet - if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) - return null; - - cancellationToken.ThrowIfCancellationRequested(); - - using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - return null; - - var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); - - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var linkedToken = linkedTokenSource.Token; + private TextSpan? TryGetCodeRefactoringSelection( + ReferenceCountedDisposable state, SnapshotSpan range) + { + _threadingContext.ThrowIfNotOnUIThread(); - // Kick off the work to get errors. - var errorTask = GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken); + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) + .ToList(); - // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - var selection = TryGetCodeRefactoringSelection(state, range); - await Task.Yield().ConfigureAwait(false); + // We only support refactorings when there is a single selection in the document. + if (selectedSpans.Count != 1) + return null; - // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. - var refactoringTask = selection != null - ? TryGetRefactoringSuggestedActionCategoryAsync(state, document, selection, fallbackOptions, linkedToken) - : SpecializedTasks.Null(); + var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - // If we happen to get the result of the error task before the refactoring task, - // and that result is non-null, we can just cancel the refactoring task. - var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); - linkedTokenSource.Cancel(); + // We only support refactorings when selected span intersects with the span that the light bulb is asking for. + if (!translatedSpan.IntersectsWith(range)) + return null; - return result == null - ? null - : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); - } + return translatedSpan.Span.ToTextSpan(); } } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index 1a78c33ae59e2..8745781c048a2 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs @@ -70,7 +70,7 @@ private async Task GetSuggestedActionsWorkerAsync( CancellationToken cancellationToken) { _threadingContext.ThrowIfNotOnUIThread(); - using var state = SourceState.TryAddReference(); + using var state = _state.TryAddReference(); if (state is null) return; From 76c0b95e11f820d5f113e9b3944f13b017f044e2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:06:38 -0700 Subject: [PATCH 0536/1047] Update src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index aa6c0676f7b95..097ff5a8bf6b1 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -259,8 +259,7 @@ private void OnTextViewClosed(object sender, EventArgs e) } } - private TextSpan? TryGetCodeRefactoringSelection( - ReferenceCountedDisposable state, SnapshotSpan range) + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) { _threadingContext.ThrowIfNotOnUIThread(); From 7632acbcebb4598cc8f5e79e2f58e9f547332b00 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:16:15 -0700 Subject: [PATCH 0537/1047] Move back --- .../Suggestions/SuggestedActionsSource.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 097ff5a8bf6b1..2df5a43055bc4 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -130,6 +130,28 @@ public Task HasSuggestedActionsAsync( private void OnTextViewClosed(object sender, EventArgs e) => Dispose(); + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) + { + _threadingContext.ThrowIfNotOnUIThread(); + + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) + .ToList(); + + // We only support refactorings when there is a single selection in the document. + if (selectedSpans.Count != 1) + return null; + + var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); + + // We only support refactorings when selected span intersects with the span that the light bulb is asking for. + if (!translatedSpan.IntersectsWith(range)) + return null; + + return translatedSpan.Span.ToTextSpan(); + } + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { using var state = _state.TryAddReference(); @@ -258,27 +280,5 @@ private void OnTextViewClosed(object sender, EventArgs e) return null; } } - - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) - { - _threadingContext.ThrowIfNotOnUIThread(); - - var selectedSpans = state.Target.TextView.Selection.SelectedSpans - .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) - .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) - .ToList(); - - // We only support refactorings when there is a single selection in the document. - if (selectedSpans.Count != 1) - return null; - - var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - - // We only support refactorings when selected span intersects with the span that the light bulb is asking for. - if (!translatedSpan.IntersectsWith(range)) - return null; - - return translatedSpan.Span.ToTextSpan(); - } } } From cc6944304f41d4976cdd4d4f56019d1df1ec1d5c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:17:19 -0700 Subject: [PATCH 0538/1047] move back --- .../Suggestions/SuggestedActionsSource.cs | 389 +++++++++--------- 1 file changed, 195 insertions(+), 194 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 2df5a43055bc4..22b0cb8618024 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -24,260 +24,261 @@ using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; -namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; - -internal partial class SuggestedActionsSourceProvider +namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions { - private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 + internal partial class SuggestedActionsSourceProvider { - private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - - private readonly ReferenceCountedDisposable _state; - private readonly IAsynchronousOperationListener _listener; - - public event EventHandler? SuggestedActionsChanged { add { } remove { } } - - private readonly IThreadingContext _threadingContext; - public readonly IGlobalOptionService GlobalOptions; - - public SuggestedActionsSource( - IThreadingContext threadingContext, - IGlobalOptionService globalOptions, - SuggestedActionsSourceProvider owner, - ITextView textView, - ITextBuffer textBuffer, - ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, - IAsynchronousOperationListener listener) + private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 { - _threadingContext = threadingContext; - GlobalOptions = globalOptions; + private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; - _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + private readonly ReferenceCountedDisposable _state; + private readonly IAsynchronousOperationListener _listener; - _state.Target.TextView.Closed += OnTextViewClosed; - _listener = listener; - } - - public void Dispose() - => _state.Dispose(); + public event EventHandler? SuggestedActionsChanged { add { } remove { } } - public bool TryGetTelemetryId(out Guid telemetryId) - { - telemetryId = default; + private readonly IThreadingContext _threadingContext; + public readonly IGlobalOptionService GlobalOptions; - using var state = _state.TryAddReference(); - if (state is null) + public SuggestedActionsSource( + IThreadingContext threadingContext, + IGlobalOptionService globalOptions, + SuggestedActionsSourceProvider owner, + ITextView textView, + ITextBuffer textBuffer, + ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, + IAsynchronousOperationListener listener) { - return false; - } + _threadingContext = threadingContext; + GlobalOptions = globalOptions; - var workspace = state.Target.Workspace; - if (workspace == null) - { - return false; - } + _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; + _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); - var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); - if (documentId == null) - { - return false; + _state.Target.TextView.Closed += OnTextViewClosed; + _listener = listener; } - var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); - if (project == null) - { - return false; - } + public void Dispose() + => _state.Dispose(); - switch (project.Language) + public bool TryGetTelemetryId(out Guid telemetryId) { - case LanguageNames.CSharp: - telemetryId = s_CSharpSourceGuid; - return true; - case LanguageNames.VisualBasic: - telemetryId = s_visualBasicSourceGuid; - return true; - case "Xaml": - telemetryId = s_xamlSourceGuid; - return true; - default: + telemetryId = default; + + using var state = _state.TryAddReference(); + if (state is null) + { return false; - } - } + } - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - => null; - - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - IUIThreadOperationContext operationContext) - => null; - - public Task HasSuggestedActionsAsync( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - { - // We implement GetSuggestedActionCategoriesAsync so this should not be called - throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); - } + var workspace = state.Target.Workspace; + if (workspace == null) + { + return false; + } - private void OnTextViewClosed(object sender, EventArgs e) - => Dispose(); + var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); + if (documentId == null) + { + return false; + } - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) - { - _threadingContext.ThrowIfNotOnUIThread(); + var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); + if (project == null) + { + return false; + } - var selectedSpans = state.Target.TextView.Selection.SelectedSpans - .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) - .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) - .ToList(); + switch (project.Language) + { + case LanguageNames.CSharp: + telemetryId = s_CSharpSourceGuid; + return true; + case LanguageNames.VisualBasic: + telemetryId = s_visualBasicSourceGuid; + return true; + case "Xaml": + telemetryId = s_xamlSourceGuid; + return true; + default: + return false; + } + } - // We only support refactorings when there is a single selection in the document. - if (selectedSpans.Count != 1) - return null; + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + => null; + + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + IUIThreadOperationContext operationContext) + => null; + + public Task HasSuggestedActionsAsync( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + { + // We implement GetSuggestedActionCategoriesAsync so this should not be called + throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); + } - var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); + private void OnTextViewClosed(object sender, EventArgs e) + => Dispose(); - // We only support refactorings when selected span intersects with the span that the light bulb is asking for. - if (!translatedSpan.IntersectsWith(range)) - return null; + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) + { + _threadingContext.ThrowIfNotOnUIThread(); - return translatedSpan.Span.ToTextSpan(); - } + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) + .ToList(); - public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) - { - using var state = _state.TryAddReference(); - if (state is null) - return null; + // We only support refactorings when there is a single selection in the document. + if (selectedSpans.Count != 1) + return null; - // Make sure the range is from the same buffer that this source was created for. - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); + var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - var workspace = state.Target.Workspace; - if (workspace == null) - return null; + // We only support refactorings when selected span intersects with the span that the light bulb is asking for. + if (!translatedSpan.IntersectsWith(range)) + return null; - // never show light bulb if solution is not fully loaded yet - if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) - return null; + return translatedSpan.Span.ToTextSpan(); + } - cancellationToken.ThrowIfCancellationRequested(); + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + using var state = _state.TryAddReference(); + if (state is null) + return null; - using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - return null; + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); + var workspace = state.Target.Workspace; + if (workspace == null) + return null; - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - // Assign over cancellation token so no one accidentally uses the wrong token. - cancellationToken = linkedTokenSource.Token; + // never show light bulb if solution is not fully loaded yet + if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) + return null; - // Kick off the work to get errors. - var errorTask = GetFixLevelAsync(); + cancellationToken.ThrowIfCancellationRequested(); - // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); + var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); + if (document == null) + return null; - var selection = TryGetCodeRefactoringSelection(state, range); - await Task.Yield().ConfigureAwait(false); + var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); - // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. - var refactoringTask = selection != null - ? TryGetRefactoringSuggestedActionCategoryAsync(selection) - : SpecializedTasks.Null(); + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + // Assign over cancellation token so no one accidentally uses the wrong token. + cancellationToken = linkedTokenSource.Token; - // If we happen to get the result of the error task before the refactoring task, - // and that result is non-null, we can just cancel the refactoring task. - var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); - linkedTokenSource.Cancel(); + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(); - return result == null - ? null - : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - async Task GetFixLevelAsync() - { - // Ensure we yield the thread that called into us, allowing it to continue onwards. + var selection = TryGetCodeRefactoringSelection(state, range); await Task.Yield().ConfigureAwait(false); - var lowPriorityAnalyzers = new ConcurrentSet(); - foreach (var order in Orderings) - { - var priority = TryGetPriority(order); - Contract.ThrowIfNull(priority); - var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(selection) + : SpecializedTasks.Null(); - var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); - if (result != null) - return result; - } + // If we happen to get the result of the error task before the refactoring task, + // and that result is non-null, we can just cancel the refactoring task. + var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); + linkedTokenSource.Cancel(); - return null; - } + return result == null + ? null + : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); - async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) - { - if (state.Target.Owner._codeFixService != null && - state.Target.SubjectBuffer.SupportsCodeFixes()) + async Task GetFixLevelAsync() { - var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( - document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); + var lowPriorityAnalyzers = new ConcurrentSet(); - if (result.HasFix) + foreach (var order in Orderings) { - Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); - return result.CodeFixCollection.FirstDiagnostic.Severity switch - { + var priority = TryGetPriority(order); + Contract.ThrowIfNull(priority); + var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); - DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, - DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, - _ => throw ExceptionUtilities.Unreachable(), - }; + var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); + if (result != null) + return result; } - if (!result.UpToDate) - return null; + return null; } - return null; - } + async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) + { + if (state.Target.Owner._codeFixService != null && + state.Target.SubjectBuffer.SupportsCodeFixes()) + { + var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( + document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); - async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) - { - // Ensure we yield the thread that called into us, allowing it to continue onwards. - await Task.Yield().ConfigureAwait(false); + if (result.HasFix) + { + Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); + return result.CodeFixCollection.FirstDiagnostic.Severity switch + { + + DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, + DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, + _ => throw ExceptionUtilities.Unreachable(), + }; + } + + if (!result.UpToDate) + return null; + } - if (!selection.HasValue) - { - // this is here to fail test and see why it is failed. - Trace.WriteLine("given range is not current"); return null; } - if (GlobalOptions.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && - state.Target.Owner._codeRefactoringService != null && - state.Target.SubjectBuffer.SupportsRefactorings()) + async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { - if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( - document, selection.Value, fallbackOptions, cancellationToken).ConfigureAwait(false)) + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); + + if (!selection.HasValue) { - return PredefinedSuggestedActionCategoryNames.Refactoring; + // this is here to fail test and see why it is failed. + Trace.WriteLine("given range is not current"); + return null; } - } - return null; + if (GlobalOptions.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && + state.Target.Owner._codeRefactoringService != null && + state.Target.SubjectBuffer.SupportsRefactorings()) + { + if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( + document, selection.Value, fallbackOptions, cancellationToken).ConfigureAwait(false)) + { + return PredefinedSuggestedActionCategoryNames.Refactoring; + } + } + + return null; + } } } } From 449f7120660a6a27a9fa05018d73248a933e4ff2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:26:59 -0700 Subject: [PATCH 0539/1047] move back --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 22b0cb8618024..be74c5242f47f 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -127,9 +127,6 @@ public Task HasSuggestedActionsAsync( throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); } - private void OnTextViewClosed(object sender, EventArgs e) - => Dispose(); - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) { _threadingContext.ThrowIfNotOnUIThread(); @@ -152,6 +149,9 @@ private void OnTextViewClosed(object sender, EventArgs e) return translatedSpan.Span.ToTextSpan(); } + private void OnTextViewClosed(object sender, EventArgs e) + => Dispose(); + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { using var state = _state.TryAddReference(); From e8bb458ca1b5cd47b211c77b281c96491f6d80a5 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 16 Apr 2024 14:04:50 -0700 Subject: [PATCH 0540/1047] Support intercepting invocation within conditional access (#72998) --- docs/features/interceptors.md | 6 +- .../SourceMethodSymbolWithAttributes.cs | 4 +- .../Portable/Syntax/SyntaxNodeExtensions.cs | 1 + .../Semantic/Semantics/InterceptorsTests.cs | 314 ++++++++++++++++++ 4 files changed, 323 insertions(+), 2 deletions(-) diff --git a/docs/features/interceptors.md b/docs/features/interceptors.md index b0b8597f4f3f7..f93209462b994 100644 --- a/docs/features/interceptors.md +++ b/docs/features/interceptors.md @@ -62,6 +62,8 @@ 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. @@ -85,7 +87,7 @@ The location of the call is the location of the simple name syntax which denotes #### Attribute creation -Roslyn provides a convenience 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. +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 @@ -181,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/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index a4c5ed6fff70e..70bbd0a42458f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -1039,11 +1039,13 @@ private void DecodeInterceptsLocationChecksumBased(DecodeWellKnownAttributeArgum 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 } }: + 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. 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/Test/Semantic/Semantics/InterceptorsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs index 63b63f19b1d28..91fa4ec491db2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterceptorsTests.cs @@ -7199,4 +7199,318 @@ static void Main() 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()); + } } From 556930ff20fe8e0c623341fa9e1197167c8fba2c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:11:51 -0700 Subject: [PATCH 0541/1047] Move to an iterative approach to determining next sibling in the blender --- .../CSharp/Portable/Parser/Blender.Cursor.cs | 45 +++++++++++++------ .../CSharp/Portable/Parser/Blender.Reader.cs | 4 +- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 4f9b6796441b9..d2dbd415c562f 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -55,28 +55,45 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) return token.Kind() == SyntaxKind.EndOfFileToken || token.FullWidth != 0; } - public Cursor MoveToNextSibling() + public static Cursor MoveToNextSibling(Cursor cursor) { - if (this.CurrentNodeOrToken.Parent != null) + var currentCursor = cursor; + while (currentCursor.CurrentNodeOrToken.UnderlyingNode != null) { - // First, look to the nodes to the right of this one in our parent's child list - // to get the next sibling. - var siblings = this.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); - for (int i = _indexInParent + 1, n = siblings.Count; i < n; i++) + var nextSibling = moveToNextSiblingWorker(currentCursor); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + currentCursor = currentCursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + + // Returns default if we should walk to the parent to try to find the next sibling. + // Returns the cursor of the next sibling if we find it. + static Cursor moveToNextSiblingWorker(Cursor cursor) + { + if (cursor.CurrentNodeOrToken.Parent != null) { - var sibling = siblings[i]; - if (IsNonZeroWidthOrIsEndOfFile(sibling)) + // First, look to the nodes to the right of this one in our parent's child list to get the next sibling. + var siblings = cursor.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); + for (int i = cursor._indexInParent + 1, n = siblings.Count; i < n; i++) { - return new Cursor(sibling, i); + var sibling = siblings[i]; + if (IsNonZeroWidthOrIsEndOfFile(sibling)) + return new Cursor(sibling, i); } + + // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is + // the next sibling of that. } - // We're at the end of this sibling chain. Walk up to the parent and see who is - // the next sibling of that. - return MoveToParent().MoveToNextSibling(); + // Don't have a parent, bail out. This will cause our caller itself to bail out. + return default; } - - return default(Cursor); } private Cursor MoveToParent() diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index f0e6877589899..4181b444e1c75 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = _oldTreeCursor.MoveToNextSibling(); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = _oldTreeCursor.MoveToNextSibling(); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); From 5a9fae0a67dfd113a9d0e957277de5c07b8a2bb0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:18:02 -0700 Subject: [PATCH 0542/1047] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index d2dbd415c562f..273db7c1982d3 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -57,16 +57,15 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) public static Cursor MoveToNextSibling(Cursor cursor) { - var currentCursor = cursor; - while (currentCursor.CurrentNodeOrToken.UnderlyingNode != null) + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { - var nextSibling = moveToNextSiblingWorker(currentCursor); + var nextSibling = moveToNextSiblingWorker(cursor); // If we got a valid sibling, return it. if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) return nextSibling; - currentCursor = currentCursor.MoveToParent(); + cursor = cursor.MoveToParent(); } // Couldn't find anything, bail out. From 210233fd6b5c361380033c4927a881ff299200f6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:21:13 -0700 Subject: [PATCH 0543/1047] Revert --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 273db7c1982d3..7f037104f0e69 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -77,7 +77,8 @@ static Cursor moveToNextSiblingWorker(Cursor cursor) { if (cursor.CurrentNodeOrToken.Parent != null) { - // First, look to the nodes to the right of this one in our parent's child list to get the next sibling. + // First, look to the nodes to the right of this one in our parent's child list + // to get the next sibling. var siblings = cursor.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); for (int i = cursor._indexInParent + 1, n = siblings.Count; i < n; i++) { @@ -91,7 +92,7 @@ static Cursor moveToNextSiblingWorker(Cursor cursor) } // Don't have a parent, bail out. This will cause our caller itself to bail out. - return default; + return default(Cursor); } } From 8c0777413120ebc5b719391aa4eea16636b37b5a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:23:17 -0700 Subject: [PATCH 0544/1047] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 7f037104f0e69..492ca7f66354a 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -59,7 +59,7 @@ public static Cursor MoveToNextSibling(Cursor cursor) { while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { - var nextSibling = moveToNextSiblingWorker(cursor); + var nextSibling = tryFindSiblingAtSameLevel(cursor); // If we got a valid sibling, return it. if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) @@ -71,9 +71,8 @@ public static Cursor MoveToNextSibling(Cursor cursor) // Couldn't find anything, bail out. return default; - // Returns default if we should walk to the parent to try to find the next sibling. - // Returns the cursor of the next sibling if we find it. - static Cursor moveToNextSiblingWorker(Cursor cursor) + // Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. + static Cursor tryFindSiblingAtSameLevel(Cursor cursor) { if (cursor.CurrentNodeOrToken.Parent != null) { From f79ba98339aefecdc65b072520a92a15b9d654f0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:27:10 -0700 Subject: [PATCH 0545/1047] Simplify --- .../CSharp/Portable/Parser/Blender.Cursor.cs | 50 +++++++------------ .../CSharp/Portable/Parser/Blender.Reader.cs | 17 +++++++ 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 492ca7f66354a..567a9b2fe477c 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -55,47 +55,35 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) return token.Kind() == SyntaxKind.EndOfFileToken || token.FullWidth != 0; } - public static Cursor MoveToNextSibling(Cursor cursor) + /// + /// Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. + /// + public Cursor TryFindNextNonEmptyOrEOFSibling() { - while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + if (this.CurrentNodeOrToken.Parent != null) { - var nextSibling = tryFindSiblingAtSameLevel(cursor); - - // If we got a valid sibling, return it. - if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) - return nextSibling; - - cursor = cursor.MoveToParent(); - } - - // Couldn't find anything, bail out. - return default; - - // Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. - static Cursor tryFindSiblingAtSameLevel(Cursor cursor) - { - if (cursor.CurrentNodeOrToken.Parent != null) + // First, look to the nodes to the right of this one in our parent's child list + // to get the next sibling. + var siblings = this.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); + for (int i = _indexInParent + 1, n = siblings.Count; i < n; i++) { - // First, look to the nodes to the right of this one in our parent's child list - // to get the next sibling. - var siblings = cursor.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); - for (int i = cursor._indexInParent + 1, n = siblings.Count; i < n; i++) + var sibling = siblings[i]; + if (IsNonZeroWidthOrIsEndOfFile(sibling)) { - var sibling = siblings[i]; - if (IsNonZeroWidthOrIsEndOfFile(sibling)) - return new Cursor(sibling, i); + return new Cursor(sibling, i); } - - // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is - // the next sibling of that. } - // Don't have a parent, bail out. This will cause our caller itself to bail out. - return default(Cursor); + // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is + // the next sibling of that. } + + // Don't have a parent, bail out. This will cause our caller itself to bail when it tries to determine + // our parent. + return default(Cursor); } - private Cursor MoveToParent() + public Cursor MoveToParent() { var parent = this.CurrentNodeOrToken.Parent; var index = IndexOfNodeInParent(parent); diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 4181b444e1c75..b75ff20d7f2ab 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -215,6 +215,23 @@ private bool TryTakeOldNodeOrToken( return true; } + private static Cursor MoveToNextSibling(Cursor cursor) + { + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + { + var nextSibling = cursor.TryFindNextNonEmptyOrEOFSibling(cursor); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + cursor = cursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + } + private bool CanReuse(SyntaxNodeOrToken nodeOrToken) { // Zero width nodes and tokens always indicate that the parser had to do From 3e9e35ba0d27200a5a7bb9c124d79c1cbacb5695 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:28:15 -0700 Subject: [PATCH 0546/1047] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 2 +- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 567a9b2fe477c..f581f66a61c90 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -58,7 +58,7 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) /// /// Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. /// - public Cursor TryFindNextNonEmptyOrEOFSibling() + public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { if (this.CurrentNodeOrToken.Parent != null) { diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index b75ff20d7f2ab..62135193d6ced 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); @@ -219,7 +219,7 @@ private static Cursor MoveToNextSibling(Cursor cursor) { while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { - var nextSibling = cursor.TryFindNextNonEmptyOrEOFSibling(cursor); + var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); // If we got a valid sibling, return it. if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) From 5725b5c6caf5104f56fa53f5c809ed9c3af10347 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:29:02 -0700 Subject: [PATCH 0547/1047] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index f581f66a61c90..37cd8d79fcc37 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -56,7 +56,8 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) } /// - /// Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. + /// Returns the cursor of our next non-empty (or EOF) sibling in our parent if one exists, or `default` if + /// if doesn't. /// public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { From 4112ba52e656a87d962c91a60851c34f16da0771 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:30:13 -0700 Subject: [PATCH 0548/1047] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 5 ----- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 37cd8d79fcc37..ad63898489e11 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -74,13 +74,8 @@ public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() return new Cursor(sibling, i); } } - - // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is - // the next sibling of that. } - // Don't have a parent, bail out. This will cause our caller itself to bail when it tries to determine - // our parent. return default(Cursor); } diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 62135193d6ced..019eb02e47f1e 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -225,6 +225,7 @@ private static Cursor MoveToNextSibling(Cursor cursor) if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) return nextSibling; + // We're at the end of this sibling chain. Walk up to the parent and see who is the next sibling of that. cursor = cursor.MoveToParent(); } From 4d5a6e60f0253b2a1e989522c18f75950f90aaca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:30:42 -0700 Subject: [PATCH 0549/1047] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 019eb02e47f1e..f5c4283ba74ed 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -225,7 +225,8 @@ private static Cursor MoveToNextSibling(Cursor cursor) if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) return nextSibling; - // We're at the end of this sibling chain. Walk up to the parent and see who is the next sibling of that. + // We're at the end of this sibling chain. Walk up to the parent and see who is + // the next sibling of that. cursor = cursor.MoveToParent(); } From e6b07f307aabbf29cd59c427bccf095a8561ffd6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:31:26 -0700 Subject: [PATCH 0550/1047] Docs --- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index f5c4283ba74ed..279195fb6d1cd 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -217,6 +217,7 @@ private bool TryTakeOldNodeOrToken( private static Cursor MoveToNextSibling(Cursor cursor) { + // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); From 699610e6ca9e92e592fedbfe918c3255af1993c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Tue, 16 Apr 2024 15:00:51 -0700 Subject: [PATCH 0551/1047] Edit and continue diagnostic source (#72787) --- .../ActiveStatementTrackingService.cs | 17 +- .../DebuggerContractVersionCheck.cs | 32 --- .../EditAndContinueDiagnosticAnalyzer.cs | 90 --------- .../EditAndContinueLanguageService.cs | 156 +++++++-------- .../IActiveStatementTrackingService.cs | 10 +- .../AlwaysActivateInProcLanguageClient.cs | 1 + .../EditAndContinueLanguageServiceTests.cs | 184 ++++++++++++++++++ .../EditAndContinueWorkspaceServiceTests.cs | 72 +++---- .../RemoteEditAndContinueServiceTests.cs | 79 ++------ .../AbstractLanguageServerProtocolTests.cs | 28 +-- .../CSharpEditAndContinueAnalyzer.cs | 14 +- .../Diagnostics/IDiagnosticAnalyzerService.cs | 3 - .../EditAndContinue/DebuggingSession.cs | 23 +-- .../EditAndContinueDiagnosticUpdateSource.cs | 130 ------------- .../EditAndContinue/EditAndContinueService.cs | 31 ++- .../Portable/EditAndContinue/EditSession.cs | 24 --- ...ider.cs => IActiveStatementSpanFactory.cs} | 2 +- .../IActiveStatementSpanLocator.cs | 19 ++ .../IEditAndContinueService.cs | 10 +- .../IEditAndContinueSessionTracker.cs | 26 +++ .../Remote/IRemoteEditAndContinueService.cs | 6 +- .../Remote/RemoteDebuggingSessionProxy.cs | 111 +++-------- .../RemoteEditAndContinueServiceProxy.cs | 86 ++------ .../API/UnitTestingHotReloadService.cs | 4 +- .../Watch/Api/WatchHotReloadService.cs | 4 +- .../Workspace/CompileTimeSolutionProvider.cs | 71 ------- ...ndContinueDiagnosticSource_OpenDocument.cs | 81 ++++++++ ...itAndContinueDiagnosticSource_Workspace.cs | 75 +++++++ .../EditAndContinueSessionState.cs | 39 ++++ ...AbstractWorkspacePullDiagnosticsHandler.cs | 91 +++++---- .../DocumentDiagnosticSource.cs | 2 + .../DocumentPullDiagnosticHandler.cs | 88 +++------ .../PublicDocumentPullDiagnosticsHandler.cs | 6 +- .../Diagnostics/PullDiagnosticCategories.cs | 5 + .../Protocol/Handler/RequestContext.cs | 23 +++ .../Diagnostics/VSInternalDiagnosticKind.cs | 5 + .../Completion/CompletionFeaturesTests.cs | 8 +- .../Diagnostics/PullDiagnosticTests.cs | 120 ++++++++++++ .../ProtocolUnitTests/HandlerTests.cs | 3 +- .../Symbols/WorkspaceSymbolsTests.cs | 6 +- .../CompileTimeSolutionProviderTests.cs | 5 - .../EditSessionActiveStatementsTests.cs | 2 +- .../MockActiveStatementSpanProvider.cs | 2 +- ...rvice.cs => MockEditAndContinueService.cs} | 43 ++-- ...odeAnalysis.Features.Test.Utilities.csproj | 1 + .../Debugger/GlassTestsHotReloadService.cs | 10 +- .../ManagedHotReloadLanguageService.cs | 6 +- .../RemoteEditAndContinueService.cs | 18 +- 48 files changed, 939 insertions(+), 933 deletions(-) delete mode 100644 src/EditorFeatures/Core/EditAndContinue/DebuggerContractVersionCheck.cs delete mode 100644 src/EditorFeatures/Core/EditAndContinue/EditAndContinueDiagnosticAnalyzer.cs create mode 100644 src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs delete mode 100644 src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs rename src/Features/Core/Portable/EditAndContinue/{IActiveStatementSpanProvider.cs => IActiveStatementSpanFactory.cs} (97%) create mode 100644 src/Features/Core/Portable/EditAndContinue/IActiveStatementSpanLocator.cs create mode 100644 src/Features/Core/Portable/EditAndContinue/IEditAndContinueSessionTracker.cs create mode 100644 src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs create mode 100644 src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs create mode 100644 src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueSessionState.cs rename src/Features/TestUtilities/EditAndContinue/{MockEditAndContinueWorkspaceService.cs => MockEditAndContinueService.cs} (74%) diff --git a/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs b/src/EditorFeatures/Core/EditAndContinue/ActiveStatementTrackingService.cs index 528a64cbacb82..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; 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 3d18cd9d0eb3c..887e433c9882c 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.BrokeredServices; @@ -26,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 @@ -44,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; @@ -70,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 @@ -80,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) { @@ -102,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) { @@ -118,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, @@ -145,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) @@ -240,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)) { @@ -267,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)) { @@ -284,7 +256,10 @@ public async ValueTask EndSessionAsync(CancellationToken cancellationToken) } } - _sourceTextProvider.Deactivate(); + // clear diagnostics reported previously: + UpdateApplyChangesDiagnostics([]); + + sourceTextProvider.Deactivate(); _debuggingSession = null; _committedDesignTimeSolution = null; _pendingUpdatedDesignTimeSolution = null; @@ -320,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) @@ -339,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) @@ -351,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/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/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index fafe7b3331b95..e4ce2a8eacb29 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -78,6 +78,7 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa // for these, independently of other diagnostics. They can also throttle themselves to not ask if // the task list would not be visible. new(PullDiagnosticCategories.Task), + new(PullDiagnosticCategories.EditAndContinue), // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic 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 c72742ee44552..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] @@ -2231,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); @@ -2243,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); @@ -2392,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: @@ -4078,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)); @@ -4469,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); @@ -4489,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 1dc31c4171205..ee50eaacbdb1f 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; @@ -37,41 +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, Obsolete] + [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(); @@ -93,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() - { - Assert.True(mockDiagnosticService.RequestedRefresh); - mockDiagnosticService.RequestedRefresh = false; - } - - 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); @@ -138,7 +122,7 @@ void VerifyReanalyzeInvocation() 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 @@ -172,17 +156,12 @@ void VerifyReanalyzeInvocation() // 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(); - - 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); @@ -234,24 +213,11 @@ void VerifyReanalyzeInvocation() }; }; - 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(); - 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); @@ -272,13 +238,7 @@ void VerifyReanalyzeInvocation() // CommitSolutionUpdate - mockEncService.CommitSolutionUpdateImpl = (out ImmutableArray documentsToReanalyze) => - { - documentsToReanalyze = ImmutableArray.Create(documentId); - }; - - await sessionProxy.CommitSolutionUpdateAsync(mockDiagnosticService, CancellationToken.None); - VerifyReanalyzeInvocation(); + await sessionProxy.CommitSolutionUpdateAsync(CancellationToken.None); // DiscardSolutionUpdate @@ -325,21 +285,12 @@ void VerifyReanalyzeInvocation() 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(); - Assert.Equal(1, emitDiagnosticsClearedCount); - emitDiagnosticsClearedCount = 0; - Assert.Empty(emitDiagnosticsUpdated); + await sessionProxy.EndDebuggingSessionAsync(CancellationToken.None); } } } diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 721f113d2db24..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); diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index b2c1e3974176b..dddf059963c97 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 diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 6b7d1a2feef80..a1521848a186c 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.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.Threading; using System.Threading.Tasks; @@ -13,8 +12,6 @@ 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; } diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index 3b926fff1dbbf..ff5a2ce294130 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, @@ -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() diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs deleted file mode 100644 index a090615ddea6d..0000000000000 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticUpdateSource.cs +++ /dev/null @@ -1,130 +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 -{ - 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() - { - } - - 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(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!)) - { - argsBuilder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - solution, - documentId.ProjectId, - documentId: documentId, - diagnostics: diagnosticData.ToImmutableArray())); - } - } - - if (projectDiagnostics.Length > 0) - { - foreach (var (projectId, diagnosticData) in projectDiagnostics.GroupBy(static data => data.ProjectId!)) - { - argsBuilder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - solution, - projectId, - documentId: null, - diagnostics: diagnosticData.ToImmutableArray())); - } - } - - if (solutionDiagnostics.Length > 0) - { - argsBuilder.Add(DiagnosticsUpdatedArgs.DiagnosticsCreated( - solution, - projectId: null, - documentId: null, - diagnostics: solutionDiagnostics)); - } - - if (argsBuilder.Count > 0) - { - updateEvent(this, argsBuilder.ToImmutableAndClear()); - } - } -} diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index ee551a3796cd7..c950c64aafaf5 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; @@ -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 456cfaac1a639..a44f2e9cad3b1 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, @@ -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; 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/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/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 44e1219eb5c59..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.RequestDiagnosticRefresh(); - - // 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.RequestDiagnosticRefresh(); - - // 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.RequestDiagnosticRefresh(); - - // report emit/apply diagnostics: - diagnosticUpdateSource.ReportDiagnostics(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.RequestDiagnosticRefresh(); } 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 dcb61c00ee82b..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; - - var result = new FixedSizeArrayBuilder(diagnosticData.Value.Length); - 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.MoveToImmutable(); - } - - 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/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/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index 1dd4931dd871d..d13938a57c7ad 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -100,7 +100,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 +114,7 @@ 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); } internal TestAccessor GetTestAccessor() diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index e9e4b2bd13245..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.ToImmutableAndClear(); - } } } 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..4f6b49fea36b6 --- /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(IDiagnosticAnalyzerService diagnosticAnalyzerService, 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..d74ca12db6421 --- /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(IDiagnosticAnalyzerService diagnosticAnalyzerService, 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(IDiagnosticAnalyzerService diagnosticAnalyzerService, 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/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index d45959627ed45..f3911586aad23 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -9,9 +9,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.TaskList; using Roslyn.LanguageServer.Protocol; @@ -62,11 +64,18 @@ protected override async ValueTask> GetOrdered if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) return []; + Contract.ThrowIfNull(context.Solution); + var category = GetDiagnosticCategory(diagnosticsParams); + // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 + if (category == PullDiagnosticCategories.Task) return GetTaskListDiagnosticSources(context, GlobalOptions); + if (category == PullDiagnosticCategories.EditAndContinue) + return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); + // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); @@ -130,6 +139,24 @@ private static ImmutableArray GetTaskListDiagnosticSources( return result.ToImmutableAndClear(); } + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// public static async ValueTask> GetDiagnosticSourcesAsync( RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) { @@ -139,7 +166,7 @@ public static async ValueTask> GetDiagnosticSo var solution = context.Solution; var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Workspace.Services.GetRequiredService(); + var codeAnalysisService = solution.Services.GetRequiredService(); foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); @@ -148,57 +175,53 @@ public static async ValueTask> GetDiagnosticSo 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); + 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); - documents = documents.AddRange(sourceGeneratedDocuments); + AddDocumentSources(sourceGeneratedDocuments); } - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - foreach (var document in documents) + // 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) { - if (!ShouldSkipDocument(context, document)) + foreach (var document in documents) { - // 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); + 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) + void AddProjectSource() { - return analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, 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/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 5e632744ee440..54f5c6ce5deb2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -32,6 +33,7 @@ public override async Task> GetDiagnosticsAsync( 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(span: null, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 37196ad7f0177..eace41f6259c5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -7,9 +7,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -69,13 +71,24 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + => progress.GetFlattenedValues(); + protected override ValueTask> GetOrderedDiagnosticSourcesAsync( VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + => new(GetDiagnosticSource(diagnosticsParams, context) is { } diagnosticSource ? [diagnosticSource] : []); + + private IDiagnosticSource? GetDiagnosticSource(VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context) { var category = diagnosticsParams.QueryingDiagnosticKind?.Value; + // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 + if (category == PullDiagnosticCategories.Task) - return new(GetDiagnosticSources(diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, GlobalOptions)); + return context.GetTrackedDocument() is { } document ? new TaskListDiagnosticSource(document, GlobalOptions) : null; + + if (category == PullDiagnosticCategories.EditAndContinue) + return GetEditAndContinueDiagnosticSource(context); var diagnosticKind = category switch { @@ -90,69 +103,28 @@ protected override ValueTask> GetOrderedDiagno }; if (diagnosticKind is null) - return new([]); - - return new(GetDiagnosticSources(diagnosticKind.Value, nonLocalDocumentDiagnostics: false, taskList: false, context, GlobalOptions)); - } + return null; - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) - { - return progress.GetFlattenedValues(); + return GetDiagnosticSource(diagnosticKind.Value, context); } - 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); + internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) + => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; - // This code path is currently only invoked from the public LSP handler, which always uses 'DiagnosticKind.All' - Debug.Assert(diagnosticKind == DiagnosticKind.All); + internal static IDiagnosticSource? GetDiagnosticSource(DiagnosticKind diagnosticKind, RequestContext context) + => context.GetTrackedDocument() is { } textDocument ? new DocumentDiagnosticSource(diagnosticKind, textDocument) : null; - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return []; + internal static IDiagnosticSource? GetNonLocalDiagnosticSource(RequestContext context, IGlobalOptionService globalOptions) + { + var textDocument = context.GetTrackedDocument(); + if (textDocument == null) + return null; - return [new NonLocalDocumentDiagnosticSource(textDocument, ShouldIncludeAnalyzer)]; + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + return null; - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); - } + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + return new NonLocalDocumentDiagnosticSource(textDocument, shouldIncludeAnalyzer: static analyzer => !analyzer.IsCompilerAnalyzer()); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 3a22a8a8ce483..400087cd41ef7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -97,7 +97,11 @@ protected override ValueTask> GetOrderedDiagno 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)); + var source = nonLocalDocumentDiagnostics + ? DocumentPullDiagnosticHandler.GetNonLocalDiagnosticSource(context, GlobalOptions) + : DocumentPullDiagnosticHandler.GetDiagnosticSource(DiagnosticKind.All, context); + + return new(source != null ? [source] : []); } protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs index 3a0243609f9b1..ca79789913f81 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticCategories.cs @@ -13,6 +13,11 @@ internal static class PullDiagnosticCategories /// 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 /// 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/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/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs index 00dfa0293382c..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, ' ', '(')); @@ -765,7 +765,7 @@ public async Task TestSoftSelectionWhenFilterTextIsEmptyForPreselectItemAsync(bo var markup = "{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + composition: Composition.AddParts(typeof(CSharpLspMockCompletionService.Factory))); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithMatchPriority(MatchPriority.Preselect); @@ -873,7 +873,7 @@ public async Task TestHandleExceptionFromGetCompletionChange(bool mutatingLspWor var markup = "Item {|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory) }.ToList()); + 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/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index f7ca6fcf4ecae..6f32ed5d1bb8a 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -5,9 +5,12 @@ 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; @@ -1187,6 +1190,123 @@ class A { } } + [Theory, CombinatorialData] + public async Task EditAndContinue_NoActiveSession(bool mutatingLspWorkspace) + { + var markup1 = "class C {}"; + + var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics: false); + + await using var testLspServer = await CreateTestLspServerAsync([markup1], LanguageNames.CSharp, mutatingLspWorkspace, options); + + 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 mutatingLspWorkspace) + { + var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics: true); + 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: true, 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: true, 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: true, category: PullDiagnosticCategories.EditAndContinue); + + AssertEx.Equal( + [ + "file:///C:/test1.cs -> [ENC_OPEN_DOC2]", + ], documentResults2.Select(Inspect)); + + var workspaceResults2 = await RunGetWorkspacePullDiagnosticsAsync( + testLspServer, useVSDiagnostics: true, previousResults: workspaceResults1.SelectAsArray(r => (r.ResultId, r.TextDocument)), 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: true, category: PullDiagnosticCategories.EditAndContinue); + AssertEx.Equal( + [ + "file:///C:/test1.cs -> []", + ], documentResults3.Select(Inspect)); + + var workspaceResults3 = await RunGetWorkspacePullDiagnosticsAsync( + testLspServer, useVSDiagnostics: true, previousResults: workspaceResults2.SelectAsArray(r => (r.ResultId, r.TextDocument)), 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) { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs index 5b811b7126bae..adb5bc1e36834 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/HandlerTests.cs @@ -112,7 +112,8 @@ public async Task CanExecuteLanguageSpecificHandlerWithDifferentRequestTypes(boo public async Task ThrowsOnInvalidLanguageSpecificHandler(bool mutatingLspWorkspace) { // Arrange - await Assert.ThrowsAsync(async () => await CreateTestLspServerAsync("", mutatingLspWorkspace, extraExportedTypes: [typeof(TestDuplicateLanguageSpecificHandler)])); + await Assert.ThrowsAsync(async () => await CreateTestLspServerAsync("", mutatingLspWorkspace, + composition: Composition.AddParts(typeof(TestDuplicateLanguageSpecificHandler)))); } [Theory, CombinatorialData] 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/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/EditSessionActiveStatementsTests.cs b/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs index 2bf099bdf75d2..9c36391c5fe08 100644 --- a/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs +++ b/src/Features/Test/EditAndContinue/EditSessionActiveStatementsTests.cs @@ -57,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; } 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/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/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/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/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); } From 7ca862f582fdc82ef0676125a5bd7aea0b41dfd9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:12:29 -0700 Subject: [PATCH 0552/1047] Simplify --- .../CSharp/Portable/Parser/Blender.Cursor.cs | 24 +++++++++++++++++-- .../CSharp/Portable/Parser/Blender.Reader.cs | 24 ++----------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index ad63898489e11..cab4183fbee13 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -59,7 +59,7 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) /// Returns the cursor of our next non-empty (or EOF) sibling in our parent if one exists, or `default` if /// if doesn't. /// - public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() + private Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { if (this.CurrentNodeOrToken.Parent != null) { @@ -79,13 +79,33 @@ public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() return default(Cursor); } - public Cursor MoveToParent() + private Cursor MoveToParent() { var parent = this.CurrentNodeOrToken.Parent; var index = IndexOfNodeInParent(parent); return new Cursor(parent, index); } + public static Cursor MoveToNextSibling(Cursor cursor) + { + // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + { + var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + // We're at the end of this sibling chain. Walk up to the parent and see who is + // the next sibling of that. + cursor = cursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + } + private static int IndexOfNodeInParent(SyntaxNode node) { if (node.Parent == null) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 279195fb6d1cd..4181b444e1c75 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); @@ -215,26 +215,6 @@ private bool TryTakeOldNodeOrToken( return true; } - private static Cursor MoveToNextSibling(Cursor cursor) - { - // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. - while (cursor.CurrentNodeOrToken.UnderlyingNode != null) - { - var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); - - // If we got a valid sibling, return it. - if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) - return nextSibling; - - // We're at the end of this sibling chain. Walk up to the parent and see who is - // the next sibling of that. - cursor = cursor.MoveToParent(); - } - - // Couldn't find anything, bail out. - return default; - } - private bool CanReuse(SyntaxNodeOrToken nodeOrToken) { // Zero width nodes and tokens always indicate that the parser had to do From e341fdd1effca1e88a65f4e56c05de2c7a637518 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:30:45 -0700 Subject: [PATCH 0553/1047] Switch to an explicit stack to avoid stack overflow when computing tree equivalence --- .../Portable/Syntax/SyntaxEquivalence.cs | 192 ++++++++++-------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index 6484b75a769c9..a41829fe01a4d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Syntax { @@ -100,130 +101,147 @@ private static bool AreTokensEquivalent(GreenNode? before, GreenNode? after, Fun private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, Func? ignoreChildNode, bool topLevel) { - if (before == after) - { - return true; - } + var stack = ArrayBuilder<(GreenNode? before, GreenNode? after)>.GetInstance(); + stack.Push((before, after)); - if (before == null || after == null) + try { - return false; - } + while (stack.Count > 0) + { + var (currentBefore, currentAfter) = stack.Pop(); + if (!areEquivalentSingleLevel(currentBefore, currentAfter)) + return false; + } - if (before.RawKind != after.RawKind) - { - return false; + return true; } - - if (before.IsToken) + finally { - Debug.Assert(after.IsToken); - return AreTokensEquivalent(before, after, ignoreChildNode); + stack.Free(); } - if (topLevel) + bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) { - // Once we get down to the body level we don't need to go any further and we can - // consider these trees equivalent. - switch ((SyntaxKind)before.RawKind) + if (before == after) { - case SyntaxKind.Block: - case SyntaxKind.ArrowExpressionClause: - return AreNullableDirectivesEquivalent(before, after, ignoreChildNode); + return true; } - // If we're only checking top level equivalence, then we don't have to go down into - // the initializer for a field. However, we can't put that optimization for all - // fields. For example, fields that are 'const' do need their initializers checked as - // changing them can affect binding results. - if ((SyntaxKind)before.RawKind == SyntaxKind.FieldDeclaration) + if (before == null || after == null) { - var fieldBefore = (Green.FieldDeclarationSyntax)before; - var fieldAfter = (Green.FieldDeclarationSyntax)after; - - var isConstBefore = fieldBefore.Modifiers.Any((int)SyntaxKind.ConstKeyword); - var isConstAfter = fieldAfter.Modifiers.Any((int)SyntaxKind.ConstKeyword); + return false; + } - if (!isConstBefore && !isConstAfter) - { - ignoreChildNode = childKind => childKind == SyntaxKind.EqualsValueClause; - } + if (before.RawKind != after.RawKind) + { + return false; } - // NOTE(cyrusn): Do we want to avoid going down into attribute expressions? I don't - // think we can avoid it as there are likely places in the compiler that use these - // expressions. For example, if the user changes [InternalsVisibleTo("goo")] to - // [InternalsVisibleTo("bar")] then that must count as a top level change as it - // affects symbol visibility. Perhaps we could enumerate the places in the compiler - // that use the values inside source attributes and we can check if we're in an - // attribute with that name. It wouldn't be 100% correct (because of annoying things - // like using aliases), but would likely be good enough for the incremental cases in - // the IDE. - } + if (before.IsToken) + { + Debug.Assert(after.IsToken); + return AreTokensEquivalent(before, after, ignoreChildNode); + } - if (ignoreChildNode != null) - { - var e1 = before.ChildNodesAndTokens().GetEnumerator(); - var e2 = after.ChildNodesAndTokens().GetEnumerator(); - while (true) + if (topLevel) { - GreenNode? child1 = null; - GreenNode? child2 = null; + // Once we get down to the body level we don't need to go any further and we can + // consider these trees equivalent. + switch ((SyntaxKind)before.RawKind) + { + case SyntaxKind.Block: + case SyntaxKind.ArrowExpressionClause: + return AreNullableDirectivesEquivalent(before, after, ignoreChildNode); + } - // skip ignored children: - while (e1.MoveNext()) + // If we're only checking top level equivalence, then we don't have to go down into + // the initializer for a field. However, we can't put that optimization for all + // fields. For example, fields that are 'const' do need their initializers checked as + // changing them can affect binding results. + if ((SyntaxKind)before.RawKind == SyntaxKind.FieldDeclaration) { - var c = e1.Current; - if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + var fieldBefore = (Green.FieldDeclarationSyntax)before; + var fieldAfter = (Green.FieldDeclarationSyntax)after; + + var isConstBefore = fieldBefore.Modifiers.Any((int)SyntaxKind.ConstKeyword); + var isConstAfter = fieldAfter.Modifiers.Any((int)SyntaxKind.ConstKeyword); + + if (!isConstBefore && !isConstAfter) { - child1 = c; - break; + ignoreChildNode = static childKind => childKind == SyntaxKind.EqualsValueClause; } } - while (e2.MoveNext()) + // NOTE(cyrusn): Do we want to avoid going down into attribute expressions? I don't + // think we can avoid it as there are likely places in the compiler that use these + // expressions. For example, if the user changes [InternalsVisibleTo("goo")] to + // [InternalsVisibleTo("bar")] then that must count as a top level change as it + // affects symbol visibility. Perhaps we could enumerate the places in the compiler + // that use the values inside source attributes and we can check if we're in an + // attribute with that name. It wouldn't be 100% correct (because of annoying things + // like using aliases), but would likely be good enough for the incremental cases in + // the IDE. + } + + if (ignoreChildNode != null) + { + var e1 = before.ChildNodesAndTokens().GetEnumerator(); + var e2 = after.ChildNodesAndTokens().GetEnumerator(); + while (true) { - var c = e2.Current; - if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + GreenNode? child1 = null; + GreenNode? child2 = null; + + // skip ignored children: + while (e1.MoveNext()) { - child2 = c; - break; + var c = e1.Current; + if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + { + child1 = c; + break; + } } - } - if (child1 == null || child2 == null) - { - // false if some children remained - return child1 == child2; - } + while (e2.MoveNext()) + { + var c = e2.Current; + if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + { + child2 = c; + break; + } + } - if (!AreEquivalentRecursive(child1, child2, ignoreChildNode, topLevel)) - { - return false; - } - } - } - else - { - // simple comparison - not ignoring children + if (child1 == null || child2 == null) + { + // false if some children remained + return child1 == child2; + } - int slotCount = before.SlotCount; - if (slotCount != after.SlotCount) - { - return false; + stack.Push((child1, child2)); + } } - - for (int i = 0; i < slotCount; i++) + else { - var child1 = before.GetSlot(i); - var child2 = after.GetSlot(i); + // simple comparison - not ignoring children - if (!AreEquivalentRecursive(child1, child2, ignoreChildNode, topLevel)) + int slotCount = before.SlotCount; + if (slotCount != after.SlotCount) { return false; } + + // Walk the children backwards so that we can push them onto the stack and continue walking in DFS order. + for (var i = slotCount - 1; i >= 0; i--) + { + var child1 = before.GetSlot(i); + var child2 = after.GetSlot(i); + stack.Push((child1, child2)); + } } + // So far these are equivalent. Continue checking the children. return true; } } From 0c115e781b98d4ac0945040b9c2bf4395df37af5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:32:10 -0700 Subject: [PATCH 0554/1047] Simplify --- src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index a41829fe01a4d..c74dc14c0f644 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -108,8 +108,8 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, { while (stack.Count > 0) { - var (currentBefore, currentAfter) = stack.Pop(); - if (!areEquivalentSingleLevel(currentBefore, currentAfter)) + (before, after) = stack.Pop(); + if (!areEquivalentSingleLevel()) return false; } @@ -120,7 +120,7 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, stack.Free(); } - bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) + bool areEquivalentSingleLevel() { if (before == after) { From e2cf02f58cfbddf9ab263c93f6a9dba34b9c2a04 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:36:36 -0700 Subject: [PATCH 0555/1047] Docs --- src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index c74dc14c0f644..85030f5a7f841 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -101,6 +101,7 @@ private static bool AreTokensEquivalent(GreenNode? before, GreenNode? after, Fun private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, Func? ignoreChildNode, bool topLevel) { + // Use an explicit stack so we can walk down deep trees without blowing the real stack. var stack = ArrayBuilder<(GreenNode? before, GreenNode? after)>.GetInstance(); stack.Push((before, after)); From c2e2e8f7b235b0e2aaf6c1ba654b9cf35546b7b7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:41:07 -0700 Subject: [PATCH 0556/1047] simplify --- src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index 85030f5a7f841..cba16e079df44 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -107,10 +107,9 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, try { - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - (before, after) = stack.Pop(); - if (!areEquivalentSingleLevel()) + if (!areEquivalentSingleLevel(current.before, current.after)) return false; } @@ -121,7 +120,7 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, stack.Free(); } - bool areEquivalentSingleLevel() + bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) { if (before == after) { From 10f8dce26b1be81e6a8d0a76bdd944f4001e2478 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:42:09 -0700 Subject: [PATCH 0557/1047] Use a simpler pattern when working with stacks --- .../AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs index 2104187597c9e..7dcd15beb0c86 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs @@ -611,10 +611,8 @@ void AddAllDocumentationComments() stack.Clear(); stack.Push(typeDeclaration); - while (stack.Count > 0) + while (stack.TryPop(out var currentType)) { - var currentType = stack.Pop(); - // Add the doc comments on the type itself. AddDocumentationComments(currentType, documentationComments); From 97e79e35f3bfe30d15f408c11699fca751179890 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:44:27 -0700 Subject: [PATCH 0558/1047] Use a simpler pattern when working with stacks --- ...edundantNullableDirectiveDiagnosticAnalyzer.cs | 3 +-- .../Compiler/Core/Extensions/StackExtensions.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) 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/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) From 151820d3f88116ec8fc7f3f5d60b6fd935ea6822 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:44:41 -0700 Subject: [PATCH 0559/1047] Use a simpler pattern when working with stacks --- ...CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs index 39ad7f7fddeb5..52084b99e4540 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForFluentDiagnosticAnalyzer.cs @@ -179,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` From 305dd15b7455974e8e8e5ef1f432870f36618dc0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:44:54 -0700 Subject: [PATCH 0560/1047] Use a simpler pattern when working with stacks --- .../CSharpIsAndCastCheckDiagnosticAnalyzer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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; From 363c7a79ba0ef5758e13bc249143e190902ff096 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:45:17 -0700 Subject: [PATCH 0561/1047] Use a simpler pattern when working with stacks --- src/Features/Core/Portable/BracePairs/IBracePairsService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 From 3cea449abb83f63ae1a0b025192139f52c980495 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:45:39 -0700 Subject: [PATCH 0562/1047] Use a simpler pattern when working with stacks --- .../AbstractSuppressionBatchFixAllProvider.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs index 9bb5419aa19c0..fd9d7e160d41c 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs @@ -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); } } }; From f817d82277b72c9e968d1261cb48e5abb7df8c3f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:46:00 -0700 Subject: [PATCH 0563/1047] Use a simpler pattern when working with stacks --- .../AbstractEmbeddedLanguageClassificationService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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) From 7c28142486ee4d72c2c2e0e461f13c07a219a052 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:46:26 -0700 Subject: [PATCH 0564/1047] Use a simpler pattern when working with stacks --- .../LanguageServices/AbstractRegexDiagnosticAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs index 3374e1e8bfaa7..eeea5cd50d59f 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EmbeddedLanguages; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Features.EmbeddedLanguages.RegularExpressions.LanguageServices; @@ -53,10 +54,9 @@ public void Analyze(SemanticModelAnalysisContext context) var stack = new 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()) { From 71c1287cea23becf6e9b24345e60519b2ecd9cef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:46:46 -0700 Subject: [PATCH 0565/1047] Use a simpler pattern when working with stacks --- .../Portable/SpellCheck/AbstractSpellCheckSpanService.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs index e2db8f7469e60..cc00957b66935 100644 --- a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs +++ b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckSpanService.cs @@ -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(); From 62116b9b4959acc3831ee769d925eab660d9bcba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:47:06 -0700 Subject: [PATCH 0566/1047] Use a simpler pattern when working with stacks --- .../BinaryExpression/AbstractBinaryExpressionWrapper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/BinaryExpression/AbstractBinaryExpressionWrapper.cs index 12d3eb0980a70..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 @@ -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); From 264204d55fee0e198f7a9406df8d3cdcb415d51b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:47:48 -0700 Subject: [PATCH 0567/1047] Use a simpler pattern when working with stacks --- .../AbstractChainedExpressionWrapper.cs | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs b/src/Features/Core/Portable/Wrapping/ChainedExpression/AbstractChainedExpressionWrapper.cs index 514a49d2c2808..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; @@ -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); } } } From d204fdaa5c98ad85ed8983416b39b3b971782cc6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:48:19 -0700 Subject: [PATCH 0568/1047] Use a simpler pattern when working with stacks --- .../Highlighting/KeywordHighlighters/AsyncAwaitHighlighter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 619bca6f97ad4b4ae637611a3c6ce6507d75ee00 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:48:36 -0700 Subject: [PATCH 0569/1047] Use a simpler pattern when working with stacks --- .../Portable/NavigationBar/CSharpNavigationBarItemService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs index ee7d28326b88c..b7d6a1416070e 100644 --- a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs +++ b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs @@ -128,12 +128,11 @@ private static IEnumerable GetTypesInFile(SemanticModel semant nodesToVisit.Push(semanticModel.SyntaxTree.GetRoot(cancellationToken)); - while (!nodesToVisit.IsEmpty()) + while (nodesToVisit.TryPop(out var node)) { if (cancellationToken.IsCancellationRequested) return []; - var node = nodesToVisit.Pop(); var type = GetType(semanticModel, node, cancellationToken); if (type != null) From 452e903d8954a5a1ff96771ad2b48d6cc2ea87c9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:50:25 -0700 Subject: [PATCH 0570/1047] Use a simpler pattern when working with stacks --- .../ObjectBrowser/AbstractListItemFactory.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs index be5372c39863d..5f8bbbc47aac1 100644 --- a/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs +++ b/src/VisualStudio/Core/Def/Library/ObjectBrowser/AbstractListItemFactory.cs @@ -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)) @@ -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)) @@ -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) From c20d607630cd264a359cdc2e4e1b25011e96fe24 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:54:14 -0700 Subject: [PATCH 0571/1047] Use a simpler pattern when working with stacks --- .../Def/ProjectSystem/Extensions/ProjectExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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)) From 7b5d716fa417edfb737e0cc88dfefcd49f8e6290 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:54:48 -0700 Subject: [PATCH 0572/1047] Use a simpler pattern when working with stacks --- .../ProjectSystem/VisualStudioProjectManagementService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs index 3c3a77866dd51..20f674aa70bab 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectManagementService.cs @@ -13,6 +13,7 @@ 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; @@ -67,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; From b98913d59078c3625ed0eea03eb91ccd0b61be1f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:55:41 -0700 Subject: [PATCH 0573/1047] Use a simpler pattern when working with stacks --- .../Core/Impl/CodeModel/MetadataNameHelpers.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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) From 12fc1db97a4220b2ca21d3f8982489b1c7505491 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:56:07 -0700 Subject: [PATCH 0574/1047] Use a simpler pattern when working with stacks --- .../Impl/CodeModel/CSharpCodeModelService_Prototype.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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('.'); From b5b70e634059aecac0d36d56a6006a92c9ec9ffe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:56:30 -0700 Subject: [PATCH 0575/1047] Use a simpler pattern when working with stacks --- .../Impl/Progression/CSharpProgressionLanguageService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 1935facc32dc838d3ab3d0292e0701e7dff564c2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:57:21 -0700 Subject: [PATCH 0576/1047] Use a simpler pattern when working with stacks --- .../AbstractSyntaxClassificationService.Worker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)) { From 47b0713c64bcc50166f810d9e3db738dc815499f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:57:52 -0700 Subject: [PATCH 0577/1047] Use a simpler pattern when working with stacks --- .../SyntaxClassification/SyntacticChangeRangeComputer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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; From ab7029dae9fcda8c306a4415acf444393001b963 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:58:03 -0700 Subject: [PATCH 0578/1047] Use a simpler pattern when working with stacks --- .../CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs index 8af7ec97ac916..a7e5192a9bdf0 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs @@ -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) { From 60e458ec6d138fe0a95bbf054be9463b665848e2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:59:46 -0700 Subject: [PATCH 0579/1047] Use a simpler pattern when working with stacks --- .../FindSymbols/FindReferences/DependentProjectsFinder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index b2a14157d4adc..225da746a1431 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -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) From 00eb18c010dd81d3d1f430ef702a1f6465a9cb48 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:00:13 -0700 Subject: [PATCH 0580/1047] Use a simpler pattern when working with stacks --- .../Portable/FindSymbols/FindReferences/FindReferenceCache.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index a5247d4839031..05efca6bffc64 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -126,9 +126,8 @@ static ImmutableArray FindMatchingIdentifierTokensFromTree( var stack = obj.Object; stack.Push(root); - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Pop(); if (current.IsNode) { foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) From c9a1b301ea1a1fa389a077d6bdc2f0ec8a5a771e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:04:05 -0700 Subject: [PATCH 0581/1047] Use a simpler pattern when working with stacks --- ...ncesSearchEngine.BidirectionalSymbolSet.cs | 10 ++++---- .../FindReferencesSearchEngine.SymbolSet.cs | 23 ++++++++----------- ...cesSearchEngine.UnidirectionalSymbolSet.cs | 12 ++++------ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs index a8ae4b1571f97..c877f21a1ee4f 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; @@ -47,15 +47,13 @@ public override ImmutableArray GetAllSymbols() 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.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..9e87a98d6648e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs @@ -46,18 +46,14 @@ public override ImmutableArray GetAllSymbols() 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); - } } } } From 26f6b78e0aa5996aaf8cec6ffb9308f8773a8117 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:06:11 -0700 Subject: [PATCH 0582/1047] Use a simpler pattern when working with stacks --- .../Extensions/INamespaceSymbolExtensions.cs | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs index 2b2102c9603a5..e08c4b96bfd6f 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().AsEnumerable()); 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()); } } From d77a067481464ee64b68c761b790327c21073305 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:07:01 -0700 Subject: [PATCH 0583/1047] Use a simpler pattern when working with stacks --- .../Storage/SQLite/v2/SQLiteConnectionPool.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs index 6d703bc07e826..f961393cb00e5 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLiteConnectionPool.cs @@ -7,6 +7,7 @@ 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; @@ -54,11 +55,8 @@ private void CloseWorker() lock (_connectionGate) { // Go through all our pooled connections and close them. - while (_connectionsPool.Count > 0) - { - var connection = _connectionsPool.Pop(); + while (_connectionsPool.TryPop(out var connection)) connection.Close_OnlyForUseBySQLiteConnectionPool(); - } } } @@ -97,10 +95,8 @@ 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. From b0a63b70f8f7d4dee0a7b9c348a46c6deccfbbf6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:09:14 -0700 Subject: [PATCH 0584/1047] Use a simpler pattern when working with stacks --- .../Compiler/Core/Collections/IntervalTree`1.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs index ae38099441407..f19e4d77bd145 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); @@ -324,9 +323,9 @@ public IEnumerator GetEnumerator() var candidates = new Stack<(Node? node, bool firstTime)>(); 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) From 0f1879b2dbbf0175f9a36c17f8fb7af9bf0361bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:09:52 -0700 Subject: [PATCH 0585/1047] Use a simpler pattern when working with stacks --- .../Extensions/INamespaceOrTypeSymbolExtensions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamespaceOrTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamespaceOrTypeSymbolExtensions.cs index e01bab1198830..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; @@ -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; } } From 37ea23273f20e9a2c06701fb42850216907edf8b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:10:21 -0700 Subject: [PATCH 0586/1047] Use a simpler pattern when working with stacks --- .../Compiler/Core/Extensions/SyntaxNodeExtensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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; From 2a7d7392d074a48ba35947d61769abb6cf770f80 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:10:36 -0700 Subject: [PATCH 0587/1047] Use a simpler pattern when working with stacks --- .../Compiler/Core/Extensions/SyntaxNodeOrTokenExtensions.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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) From 5f12902b768b82abe8ab0e402d7075b94298df92 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:11:37 -0700 Subject: [PATCH 0588/1047] Use a simpler pattern when working with stacks --- .../Compiler/Core/Formatting/ContextIntervalTree.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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)) { From 7ea7c174a29eda91e3a8131ad46c88bd1239a171 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:12:17 -0700 Subject: [PATCH 0589/1047] Use a simpler pattern when working with stacks --- .../Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs index 2db314abcbf02..08e260c1fa9e8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs @@ -49,9 +49,9 @@ public static bool IsOnSingleLine(this ISyntaxFacts syntaxFacts, SyntaxNode node 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 From 15a808a58cb0a58f318100eca80befbe2c67c939 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:13:04 -0700 Subject: [PATCH 0590/1047] Use a simpler pattern when working with stacks --- ...ParenthesizedExpressionSyntaxExtensions.cs | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ParenthesizedExpressionSyntaxExtensions.cs index e276008785309..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; } From a70433d0ef44f1bd874309c3cd3de0d93ad5371d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:13:19 -0700 Subject: [PATCH 0591/1047] Use a simpler pattern when working with stacks --- .../Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 47ca8fdd13dd0..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); From 0c99dd5e73483dd597385f806faf30b0ff6a33b1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:13:54 -0700 Subject: [PATCH 0592/1047] Use a simpler pattern when working with stacks --- .../CodeFixes/ForkingSyntaxEditorBasedCodeFixProvider.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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(); From 4a8806f69e0bc2af14cfa0869a4aa44c749a9d79 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:16:11 -0700 Subject: [PATCH 0593/1047] Use a simpler pattern when working with stacks --- .../Portable/NavigationBar/CSharpNavigationBarItemService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs index b7d6a1416070e..d0bc4e043f363 100644 --- a/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs +++ b/src/Features/CSharp/Portable/NavigationBar/CSharpNavigationBarItemService.cs @@ -124,7 +124,7 @@ 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)); From 288c8262aa525e12c520b3bcca6c731c195b9152 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:18:51 -0700 Subject: [PATCH 0594/1047] Use a simpler pattern when working with stacks --- .../LanguageServices/AbstractRegexDiagnosticAnalyzer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs b/src/Features/Core/Portable/EmbeddedLanguages/RegularExpressions/LanguageServices/AbstractRegexDiagnosticAnalyzer.cs index eeea5cd50d59f..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,7 @@ 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; @@ -51,7 +52,7 @@ 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.TryPop(out var current)) From 2e636ff7c40d06151d8de7d56f8037b5ad5bcf97 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:23:37 -0700 Subject: [PATCH 0595/1047] Simplify --- .../Portable/Shared/Extensions/INamespaceSymbolExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs index e08c4b96bfd6f..77f2d60898931 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/INamespaceSymbolExtensions.cs @@ -66,7 +66,7 @@ public static IEnumerable GetAllNamespacesAndTypes( cancellationToken.ThrowIfCancellationRequested(); if (current is INamespaceSymbol childNamespace) { - stack.AddRange(childNamespace.GetMembers().AsEnumerable()); + stack.AddRange(childNamespace.GetMembers()); yield return childNamespace; } else From ecff8dd52ea5280246fa033968f2a83ef959556e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:24:38 -0700 Subject: [PATCH 0596/1047] Use a simpler pattern when working with stacks --- .../Compiler/Core/Collections/IntervalTree`1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs index f19e4d77bd145..0e0c3b9ca927a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTree`1.cs @@ -321,7 +321,7 @@ 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.TryPop(out var tuple)) { From 66e9c656c3eccf034b541231fcb1edc9fdc6be82 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 16:25:35 -0700 Subject: [PATCH 0597/1047] Use a simpler pattern when working with stacks --- .../Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs index 08e260c1fa9e8..04668927a3f0c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs @@ -36,13 +36,12 @@ 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; } From 6291b1c0cc666f0ee441bbe542a1df32f028a637 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 11 Apr 2024 12:12:48 -0700 Subject: [PATCH 0598/1047] Restore `dynamic` as result type of some operations involving `dynamic` arguments (#72964) Fixes #72750. For C# 12 language version: behavior of the compiler in regards to deciding between whether binding should be static or dynamic is the same as behavior of C# 12 compiler. As a result, for affected scenarios, what used to have `dynamic` type in C# 12 compiler will have `dynamic` type when C# 12 language version is targeted. For newer language versions: invocations statically bound in presence of dynamic arguments should have dynamic result if their dynamic binding succeeded in C# 12. Corresponding spec update - dotnet/csharplang#8027 at commit 8. Related to #33011, #72906, #72912, #72913, #72914, #72916, #72931. --- .../Portable/Binder/Binder.ValueChecks.cs | 30 +- .../Portable/Binder/Binder_Deconstruct.cs | 63 +- .../Portable/Binder/Binder_Expressions.cs | 56 +- .../Portable/Binder/Binder_Invocation.cs | 86 +- .../Portable/Binder/Binder_Operators.cs | 78 +- .../Portable/Binder/Binder_Statements.cs | 9 +- .../Compilation/CSharpSemanticModel.cs | 5 +- .../Portable/FlowAnalysis/NullableWalker.cs | 155 +- .../LocalRewriter_AssignmentOperator.cs | 61 +- .../LocalRewriter/LocalRewriter_Call.cs | 26 +- ...ocalRewriter_CompoundAssignmentOperator.cs | 7 +- .../LocalRewriter/LocalRewriter_Conversion.cs | 6 +- ...writer_DeconstructionAssignmentOperator.cs | 42 +- .../LocalRewriter/LocalRewriter_Event.cs | 9 +- .../LocalRewriter_IndexerAccess.cs | 26 +- .../LocalRewriter_LocalDeclaration.cs | 1 - ...writer_NullCoalescingAssignmentOperator.cs | 12 +- ...ObjectOrCollectionInitializerExpression.cs | 17 +- .../LocalRewriter_UnaryOperator.cs | 204 +- .../LocalRewriter_UsingStatement.cs | 2 +- .../Lowering/SyntheticBoundNodeFactory.cs | 16 +- ...perationTests_IObjectCreationExpression.cs | 36 + .../Test/Semantic/Semantics/DynamicTests.cs | 7007 ++++++++++++++++- .../Test/Semantic/Semantics/OperatorTests.cs | 10 +- ...nticModelGetSemanticInfoTests_LateBound.cs | 6 +- 25 files changed, 7696 insertions(+), 274 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 002b0a25e6cb1..48a9ac8a41427 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -310,8 +310,19 @@ private static bool RequiresRefOrOut(BindValueKind kind) #nullable enable - private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + var useSetAccessor = valueKind == BindValueKind.Assignable && !indexerAccess.Indexer.ReturnsByRef; var accessorForDefaultArguments = useSetAccessor ? indexerAccess.Indexer.GetOwnOrInheritedSetMethod() @@ -404,15 +415,26 @@ private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundI /// method returns a BoundBadExpression node. The method returns the original /// expression without generating any error if the expression has errors. /// - private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + switch (expr.Kind) { case BoundKind.PropertyGroup: expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics); if (expr is BoundIndexerAccess indexerAccess) { - expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); } break; @@ -430,7 +452,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind return expr; case BoundKind.IndexerAccess: - expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); break; case BoundKind.UnconvertedObjectCreationExpression: diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 0e9c9f8b4bcb7..9f8e70e818d03 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -128,7 +128,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( var type = boundRHS.Type ?? voidType; return new BoundDeconstructionAssignmentOperator( node, - DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true), + DeconstructionVariablesAsTuple(left, checkedVariables, assignmentResultTupleType: out _, diagnostics, ignoreDiagnosticsFromTuple: true), new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: null, type: type, hasErrors: true), resultIsUsed, @@ -154,9 +154,9 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( FailRemainingInferences(checkedVariables, diagnostics); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, out NamedTypeSymbol returnType, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); Debug.Assert(hasErrors || lhsTuple.Type is object); - TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type!; + returnType = hasErrors ? CreateErrorType() : returnType; var boundConversion = new BoundConversion( boundRHS.Syntax, @@ -316,8 +316,8 @@ private bool MakeDeconstructionConversion( } else { - var single = variable.Single; - Debug.Assert(single is object); + Debug.Assert(variable.Single is object); + var single = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _); Debug.Assert(single.Type is not null); CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); @@ -502,7 +502,7 @@ private string GetDebuggerDisplay() if ((object?)variable.Single.Type != null) { // typed-variable on the left - mergedType = variable.Single.Type; + mergedType = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _).Type; } } } @@ -542,11 +542,14 @@ private string GetDebuggerDisplay() syntax: syntax); } - private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables, + private BoundTupleExpression DeconstructionVariablesAsTuple( + CSharpSyntaxNode syntax, ArrayBuilder variables, + out NamedTypeSymbol assignmentResultTupleType, BindingDiagnosticBag diagnostics, bool ignoreDiagnosticsFromTuple) { int count = variables.Count; var valuesBuilder = ArrayBuilder.GetInstance(count); + var resultTypesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var typesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var locationsBuilder = ArrayBuilder.GetInstance(count); var namesBuilder = ArrayBuilder.GetInstance(count); @@ -554,18 +557,24 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn foreach (var variable in variables) { BoundExpression value; + TypeSymbol resultType; if (variable.NestedVariables is object) { - value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, ignoreDiagnosticsFromTuple); + value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, out NamedTypeSymbol nestedResultType, diagnostics, ignoreDiagnosticsFromTuple); + resultType = nestedResultType; namesBuilder.Add(null); } else { Debug.Assert(variable.Single is object); value = variable.Single; + Debug.Assert(value.Type is not null); + resultType = value.Type; + value = AdjustAssignmentTargetForDynamic(value, forceDynamicResult: out _); namesBuilder.Add(ExtractDeconstructResultElementName(value)); } valuesBuilder.Add(value); + resultTypesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(resultType)); typesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(value.Type)); locationsBuilder.Add(variable.Syntax.Location); } @@ -579,14 +588,39 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn ImmutableArray inferredPositions = tupleNames.IsDefault ? default : tupleNames.SelectAsArray(n => n != null); bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames(); + ImmutableArray elementLocations = locationsBuilder.ToImmutableAndFree(); + var createTupleDiagnostics = ignoreDiagnosticsFromTuple ? null : BindingDiagnosticBag.GetInstance(diagnostics); + var type = NamedTypeSymbol.CreateTuple( syntax.Location, - typesWithAnnotationsBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(), + typesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, + tupleNames, this.Compilation, + shouldCheckConstraints: createTupleDiagnostics is not null, + includeNullability: false, + errorPositions: disallowInferredNames ? inferredPositions : default, + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is { AccumulatesDiagnostics: true, DiagnosticBag: { } bag } && + bag.HasAnyResolvedErrors()) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + createTupleDiagnostics = null; // Suppress possibly duplicate errors from CreateTuple call below. + } + + // This type is the same as the 'type' above, or differs only by using 'dynamic' for some elements. + assignmentResultTupleType = NamedTypeSymbol.CreateTuple( + syntax.Location, + resultTypesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, tupleNames, this.Compilation, - shouldCheckConstraints: !ignoreDiagnosticsFromTuple, + shouldCheckConstraints: createTupleDiagnostics is not null, includeNullability: false, errorPositions: disallowInferredNames ? inferredPositions : default, - syntax: syntax, diagnostics: ignoreDiagnosticsFromTuple ? null : diagnostics); + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is not null) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + } return (BoundTupleExpression)BindToNaturalType(new BoundTupleLiteral(syntax, arguments, tupleNames, inferredPositions, type), diagnostics); } @@ -788,12 +822,17 @@ private DeconstructionVariable BindDeconstructionVariables( } default: var boundVariable = BindExpression(node, diagnostics, invoked: false, indexed: false); - var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics); + var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + if (expression == null && checkedVariable.Kind != BoundKind.DiscardExpression) { expression = node; } + // This object doesn't escape BindDeconstruction method, we don't call AdjustAssignmentTargetForDynamic + // for checkedVariable here, instead we call it where binder accesses DeconstructionVariable.Single + // In some of the places we need to be able to detect the fact that the type used to be dynamic, and, + // if we erase the fact here, there will be no other place for us to look at. return new DeconstructionVariable(checkedVariable, node); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index ec89bd55c711e..0c95019c2627d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -233,10 +233,10 @@ internal NamedTypeSymbol CreateErrorType(string name = "") /// did not meet the requirements, the return value will be a that /// (typically) wraps the subexpression. /// - internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind) + internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind, bool dynamificationOfAssignmentResultIsHandled = false) { var result = this.BindExpression(node, diagnostics: diagnostics, invoked: false, indexed: false); - return CheckValue(result, valueKind, diagnostics); + return CheckValue(result, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled); } internal BoundExpression BindRValueWithoutTargetType(ExpressionSyntax node, BindingDiagnosticBag diagnostics, bool reportNoTargetType = true) @@ -5703,7 +5703,7 @@ private BoundExpression BindObjectInitializerMember( { // D = { ..., = , ... }, where D : dynamic boundMember = new BoundDynamicObjectInitializerMember(leftSyntax, memberName.Identifier.Text, implicitReceiver.Type, initializerType, hasErrors: false); - return CheckValue(boundMember, valueKind, diagnostics); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); } else { @@ -5806,7 +5806,15 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.IndexerAccess: { - var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics); + var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + + Debug.Assert(valueKind is BindValueKind.RValue or BindValueKind.RefAssignable or BindValueKind.Assignable); + + if (valueKind == BindValueKind.Assignable) + { + indexer = (BoundIndexerAccess)AdjustAssignmentTargetForDynamic(indexer, forceDynamicResult: out _); + } + boundMember = indexer; hasErrors |= isRhsNestedInitializer && !CheckNestedObjectInitializerPropertySymbol(indexer.Indexer, leftSyntax, diagnostics, hasErrors, ref resultKind); arguments = indexer.Arguments; @@ -5846,7 +5854,10 @@ private BoundExpression BindObjectInitializerMember( hasErrors |= !CheckNestedObjectInitializerPropertySymbol(property, leftSyntax, diagnostics, hasErrors, ref resultKind); } - return hasErrors ? boundMember : CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return hasErrors ? + boundMember : + CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); case BoundKind.DynamicObjectInitializerMember: break; @@ -5863,7 +5874,8 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.ArrayAccess: case BoundKind.PointerElementAccess: - return CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); default: return BadObjectInitializerMemberAccess(boundMember, implicitReceiver, leftSyntax, diagnostics, valueKind, hasErrors); @@ -5948,7 +5960,8 @@ private BoundExpression BadObjectInitializerMemberAccess( break; case LookupResultKind.Inaccessible: - boundMember = CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + boundMember = CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); Debug.Assert(boundMember.HasAnyErrors); break; @@ -9719,13 +9732,14 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( argumentSyntax, singleCandidate); } } - else + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(finalApplicableCandidates[0])) { var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); resultWithSingleCandidate.ResultsBuilder.Add(finalApplicableCandidates[0]); overloadResolutionResult.Free(); - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, hasDynamicArgument: true, diagnostics); } } @@ -9741,7 +9755,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( return BindDynamicIndexer(syntax, receiver, analyzedArguments, finalApplicableCandidates.SelectAsArray(r => r.Member), diagnostics); } - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, hasDynamicArgument: false, diagnostics); } private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( @@ -9750,6 +9764,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( ArrayBuilder propertyGroup, AnalyzedArguments analyzedArguments, OverloadResolutionResult overloadResolutionResult, + bool hasDynamicArgument, BindingDiagnosticBag diagnostics) { BoundExpression propertyAccess; @@ -9811,6 +9826,25 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( MemberResolutionResult resolutionResult = overloadResolutionResult.ValidResult; PropertySymbol property = resolutionResult.Member; + bool forceDynamicResultType = false; + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(property.Type.IsDynamic() || property.ReturnsByRef || + !Conversions.ClassifyConversionFromExpressionType(property.Type, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(resolutionResult))) + { + var tryDynamicAccessDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicIndexer(syntax, receiver, analyzedArguments, ImmutableArray.Create(property), tryDynamicAccessDiagnostics); + forceDynamicResultType = !tryDynamicAccessDiagnostics.HasAnyResolvedErrors(); + tryDynamicAccessDiagnostics.Free(); + } + + diagnostics.Add(syntax, useSiteInfo); + ReportDiagnosticsIfObsolete(diagnostics, property, syntax, hasBaseReceiver: receiver != null && receiver.Kind == BoundKind.BaseReference); // Make sure that the result of overload resolution is valid. @@ -9841,7 +9875,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( expanded: resolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm, argsToParams, defaultArguments: default, - property.Type, + forceDynamicResultType ? Compilation.DynamicType : property.Type, gotError); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 3b7c36fb9997c..948ce167d500b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -638,14 +638,19 @@ private BoundExpression BindDelegateInvocation( result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(applicable)) + { + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: true, boundExpression, diagnostics, queryClause); + } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: false, boundExpression, diagnostics, queryClause); } overloadResolutionResult.Free(); @@ -682,6 +687,13 @@ private bool HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(MemberResolutionResult candidate) + where TMember : Symbol + { + return candidate.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && + !candidate.Member.GetParameters().Last().Type.IsSZArray(); + } + private BoundExpression BindMethodGroupInvocation( SyntaxNode syntax, SyntaxNode expression, @@ -756,7 +768,7 @@ private BoundExpression BindMethodGroupInvocation( // we want to force any unbound lambda arguments to cache an appropriate conversion if possible; see 9448. result = BindInvocationExpressionContinued( syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments, - resolution.MethodGroup, delegateTypeOpt: null, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause); + resolution.MethodGroup, delegateTypeOpt: null, hasDynamicArgument: false, methodGroup, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause); } // Since the resolution is non-empty and has no diagnostics, the LookupResultKind in its MethodGroup is uninteresting. @@ -820,7 +832,7 @@ private BoundExpression BindMethodGroupInvocation( { result = BindInvocationExpressionContinued( syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments, - resolution.MethodGroup, delegateTypeOpt: null, diagnostics: diagnostics, queryClause: queryClause); + resolution.MethodGroup, delegateTypeOpt: null, hasDynamicArgument: false, methodGroup, diagnostics: diagnostics, queryClause: queryClause); } } } @@ -975,23 +987,33 @@ private BoundExpression TryEarlyBindSingleCandidateInvocationWithDynamicArgument return null; } - var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); - resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for local functions or expanded non-array params cases. + if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || + singleCandidate.MethodKind == MethodKind.LocalFunction || + IsMemberWithExpandedNonArrayParamsCollection(methodResolutionResult)) + { + var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); + resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); - BoundExpression result = BindInvocationExpressionContinued( - node: syntax, - expression: expression, - methodName: methodName, - result: resultWithSingleCandidate, - analyzedArguments: resolution.AnalyzedArguments, - methodGroup: resolution.MethodGroup, - delegateTypeOpt: null, - diagnostics: diagnostics, - queryClause: queryClause); + BoundExpression result = BindInvocationExpressionContinued( + node: syntax, + expression: expression, + methodName: methodName, + result: resultWithSingleCandidate, + analyzedArguments: resolution.AnalyzedArguments, + methodGroup: resolution.MethodGroup, + delegateTypeOpt: null, + hasDynamicArgument: true, + boundMethodGroup, + diagnostics: diagnostics, + queryClause: queryClause); - resultWithSingleCandidate.Free(); + resultWithSingleCandidate.Free(); - return result; + return result; + } + + return null; } private ImmutableArray> GetCandidatesPassingFinalValidation( @@ -1130,6 +1152,8 @@ private BoundCall BindInvocationExpressionContinued( AnalyzedArguments analyzedArguments, MethodGroup methodGroup, NamedTypeSymbol delegateTypeOpt, + bool hasDynamicArgument, + BoundExpression targetMethodGroupOrDelegateInstance, BindingDiagnosticBag diagnostics, CSharpSyntaxNode queryClause = null) { @@ -1205,12 +1229,32 @@ private BoundCall BindInvocationExpressionContinued( GetOriginalMethods(result), methodGroup.ResultKind, methodGroup.TypeArguments.ToImmutable(), analyzedArguments, invokedAsExtensionMethod: invokedAsExtensionMethod, isDelegate: ((object)delegateTypeOpt != null)); } - // Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate. + // Otherwise, overload resolution found a unique best candidate. // We still have to determine if it passes final validation. var methodResult = result.ValidResult; var returnType = methodResult.Member.ReturnType; var method = methodResult.Member; + bool forceDynamicResultType = false; + + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(methodGroup.IsExtensionMethodGroup || method.MethodKind == MethodKind.LocalFunction || + method.ReturnsVoid || method.ReturnsByRef || returnType.IsDynamic() || + !Conversions.ClassifyConversionFromExpressionType(returnType, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(methodResult))) + { + var tryDynamicInvocationDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicInvocation(node, targetMethodGroupOrDelegateInstance, analyzedArguments, ImmutableArray.Create(method), tryDynamicInvocationDiagnostics, queryClause); + forceDynamicResultType = !tryDynamicInvocationDiagnostics.HasAnyResolvedErrors(); + tryDynamicInvocationDiagnostics.Free(); + } + + diagnostics.Add(node, useSiteInfo); // It is possible that overload resolution succeeded, but we have chosen an // instance method and we're in a static method. A careful reading of the @@ -1340,7 +1384,9 @@ private BoundCall BindInvocationExpressionContinued( return new BoundCall(node, receiver, initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(receiver, method), method, args, argNames, argRefKinds, isDelegateCall: isDelegateCall, expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, type: returnType, hasErrors: gotError); + argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, + type: forceDynamicResultType ? Compilation.DynamicType : returnType, + hasErrors: gotError); } #nullable enable diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 40acd645365d9..1b22a794af71d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -22,7 +22,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, { node.Left.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind())); + BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind()), dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(left, diagnostics); BoundExpression right = BindValue(node.Right, diagnostics, BindValueKind.RValue); BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind()); @@ -43,6 +43,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, } } + left = AdjustAssignmentTargetForDynamic(left, out bool forceDynamicResult); + if (left.HasAnyErrors || right.HasAnyErrors) { // NOTE: no overload resolution candidates. @@ -81,7 +83,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, finalPlaceholder: placeholder, finalConversion: conversion, LookupResultKind.Viable, - left.Type, + AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasErrors: false); } else @@ -244,7 +246,56 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, var leftConversion = CreateConversion(node.Left, leftPlaceholder, best.LeftConversion, isCast: false, conversionGroupOpt: null, best.Signature.LeftType, diagnostics); return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted, - leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError); + leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasError); + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "reverting back to the indexer's type for the target" part. + /// See for the helper for the second part. + /// + private static BoundExpression AdjustAssignmentTargetForDynamic(BoundExpression target, out bool forceDynamicResult) + { + if (target is BoundIndexerAccess { Type.TypeKind: TypeKind.Dynamic, Indexer.Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + forceDynamicResult = true; + target = indexerAccess.Update( + indexerAccess.ReceiverOpt, + indexerAccess.InitialBindingReceiverIsSubjectToCloning, + indexerAccess.Indexer, + indexerAccess.Arguments, + indexerAccess.ArgumentNamesOpt, + indexerAccess.ArgumentRefKindsOpt, + indexerAccess.Expanded, + indexerAccess.ArgsToParamsOpt, + indexerAccess.DefaultArguments, + indexerAccess.Indexer.Type); + } + else + { + forceDynamicResult = false; + } + + return target; + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "setting result type of the assignment to 'dynamic' type" part. + /// See helper for the first part. + /// + TypeSymbol AdjustAssignmentTypeToDynamicIfNecessary(TypeSymbol leftType, bool forceDynamicResult) + { + return forceDynamicResult ? Compilation.DynamicType : leftType; } /// @@ -2261,7 +2312,9 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS { operandSyntax.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement), diagnostics); + BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement, dynamificationOfAssignmentResultIsHandled: true), + diagnostics); + UnaryOperatorKind kind = SyntaxKindToUnaryOperatorKind(node.Kind()); // If the operand is bad, avoid generating cascading errors. @@ -2283,6 +2336,8 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS hasErrors: true); } + operand = AdjustAssignmentTargetForDynamic(operand, out bool forceDynamicResult); + // The operand has to be a variable, property or indexer, so it must have a type. var operandType = operand.Type; Debug.Assert((object)operandType != null); @@ -2369,7 +2424,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS resultConversion, resultKind, originalUserDefinedOperators, - operandType, + AdjustAssignmentTypeToDynamicIfNecessary(operandType, forceDynamicResult), hasErrors); } @@ -4134,8 +4189,11 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node.OperatorToken); - BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment); + BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(leftOperand, diagnostics); + + leftOperand = AdjustAssignmentTargetForDynamic(leftOperand, out bool forceDynamicResult); + BoundExpression rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue); // If either operand is bad, bail out preventing more cascading errors @@ -4170,7 +4228,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { diagnostics.Add(node, useSiteInfo); var convertedRightOperand = CreateConversion(rightOperand, underlyingRightConversion, underlyingLeftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, underlyingLeftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(underlyingLeftType, forceDynamicResult)); + Debug.Assert(result.IsNullableValueTypeAssignment); + return result; } } @@ -4184,7 +4244,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio if (rightConversion.Exists) { var convertedRightOperand = CreateConversion(rightOperand, rightConversion, leftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, leftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(leftType, forceDynamicResult)); + Debug.Assert(!result.IsNullableValueTypeAssignment); + return result; } // a and b are incompatible and a compile-time error occurs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 23271214ea97a..5fbaa109f9bed 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1429,9 +1429,11 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD if (isRef) MessageID.IDS_FeatureRefReassignment.CheckFeatureAvailability(diagnostics, node.Right.GetFirstToken()); - var op1 = BindValue(node.Left, diagnostics, lhsKind); + var op1 = BindValue(node.Left, diagnostics, lhsKind, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(op1, diagnostics); + op1 = AdjustAssignmentTargetForDynamic(op1, out bool forceDynamicResult); + var rhsKind = isRef ? GetRequiredRHSValueKindForRefAssignment(op1) : BindValueKind.RValue; var op2 = BindValue(rhsExpr, diagnostics, rhsKind); @@ -1442,7 +1444,10 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD op1 = InferTypeForDiscardAssignment((BoundDiscardExpression)op1, op2, diagnostics); } - return BindAssignment(node, op1, op2, isRef, diagnostics); + BoundAssignmentOperator result = BindAssignment(node, op1, op2, isRef, diagnostics); + result = result.Update(result.Left, result.Right, result.IsRef, AdjustAssignmentTypeToDynamicIfNecessary(result.Type, forceDynamicResult)); + + return result; } private static BindValueKind GetRequiredRHSValueKindForRefAssignment(BoundExpression boundLeft) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index df844fd007afa..4a54eece1b5e2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -3847,9 +3847,10 @@ private static void GetSymbolsAndResultKind(BoundIncrementOperator increment, ou { Debug.Assert((object)increment.MethodOpt == null && increment.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty); UnaryOperatorKind op = increment.OperatorKind.Operator(); - symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(increment.Operand.Type.StrippedType(), + TypeSymbol opType = increment.Operand.Type.StrippedType(); + symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(opType, OperatorFacts.UnaryOperatorNameFromOperatorKind(op, isChecked: increment.OperatorKind.IsChecked()), - increment.Type.StrippedType())); + opType)); resultKind = increment.ResultKind; } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index bf4e997c62729..eb65524243bd8 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4081,7 +4081,8 @@ void setAnalyzedNullabilityAsContinuation( if (symbol != null) { - Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); + Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes) || + (symbol is PropertySymbol { IsIndexer: true } && objectInitializer.Type.IsDynamic())); symbol = AsMemberOfType(containingType, symbol); } @@ -5440,20 +5441,35 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly LearnFromNonNullTest(leftOperand, ref leftState); LearnFromNullTest(leftOperand, ref this.State); + var adjustedNodeType = node.Type; + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(node, leftOperand)) + { + Debug.Assert(leftOperand.Type is not null); + + if (node.IsNullableValueTypeAssignment) + { + adjustedNodeType = leftOperand.Type.GetNullableUnderlyingType(); + } + else + { + adjustedNodeType = leftOperand.Type; + } + } + // If we are assigning to a nullable value type variable, set the top-level state of // the LHS first, then change the slot to the Value property of the LHS to simulate // assignment of the RHS and update the nullable state of the underlying value type. if (node.IsNullableValueTypeAssignment) { Debug.Assert(targetType.Type.ContainsErrorType() || - node.Type?.ContainsErrorType() == true || - TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), node.Type, TypeCompareKind.AllIgnoreOptions)); + adjustedNodeType?.ContainsErrorType() == true || + TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), adjustedNodeType, TypeCompareKind.AllIgnoreOptions)); if (leftSlot > 0) { SetState(ref this.State, leftSlot, NullableFlowState.NotNull); leftSlot = GetNullableOfTValueSlot(targetType.Type, leftSlot, out _); } - targetType = TypeWithAnnotations.Create(node.Type, NullableAnnotation.NotAnnotated); + targetType = TypeWithAnnotations.Create(adjustedNodeType, NullableAnnotation.NotAnnotated); } TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand), trackMembers: false, AssignmentKind.Assignment); @@ -5462,10 +5478,49 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly Join(ref this.State, ref leftState); TypeWithState resultType = TypeWithState.Create(targetType.Type, rightResult.State); - SetResultType(node, resultType); + + if (adjustedNodeType != (object?)node.Type) + { + Debug.Assert(adjustedNodeType is not null); + SetDynamicResult(node, resultType); + } + else + { + SetResultType(node, resultType); + } + return null; } + /// + /// When an operation on an indexer with dynamic argument is resolved statically, + /// in some scenarios result type of the operation is set to 'dynamic' type. + /// + /// This helper takes care of the setting result type to 'dynamic' type. + /// + private void SetDynamicResult(BoundExpression node, TypeWithState sourceType) + { + Debug.Assert(node.Type is not null); + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(sourceType.Type is not null); + Debug.Assert(!sourceType.Type.IsDynamic()); + Debug.Assert(!sourceType.Type.IsVoidType()); + + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + + SetResultType(node, + VisitConversion( + conversionOpt: null, + conversionOperand: node, + _conversions.ClassifyConversionFromExpressionType(sourceType.Type, node.Type, isChecked: false, ref discardedUseSiteInfo), + targetTypeWithNullability: TypeWithAnnotations.Create(node.Type, NullableAnnotation.Annotated), + operandType: sourceType, + checkConversion: false, + fromExplicitCast: false, + useLegacyWarnings: false, + AssignmentKind.Assignment)); + } + public override BoundNode? VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { Debug.Assert(!IsConditionalState); @@ -6054,7 +6109,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr VisitResult? extensionReceiverResult = null; while (true) { - ReinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); + reinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); receiver = node; if (!calls.TryPop(out node!)) @@ -6090,7 +6145,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr else { TypeWithState receiverType = visitAndCheckReceiver(node); - ReinferMethodAndVisitArguments(node, receiverType); + reinferMethodAndVisitArguments(node, receiverType); } return null; @@ -6129,35 +6184,44 @@ TypeWithState visitAndCheckReceiver(BoundCall node) return receiverType; } - } - private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) - { - var method = node.Method; - ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; - if (!receiverType.HasNullType) + void reinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) { - // Update method based on inferred receiver type. - method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); - } + var method = node.Method; + ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; + if (!receiverType.HasNullType) + { + // Update method based on inferred receiver type. + method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); + } - ImmutableArray results; - bool returnNotNull; - (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, - node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); + ImmutableArray results; + bool returnNotNull; + (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, + node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); - ApplyMemberPostConditions(node.ReceiverOpt, method); + ApplyMemberPostConditions(node.ReceiverOpt, method); - LearnFromEqualsMethod(method, node, receiverType, results); + LearnFromEqualsMethod(method, node, receiverType, results); - var returnState = GetReturnTypeWithState(method); - if (returnNotNull) - { - returnState = returnState.WithNotNullState(); - } + var returnState = GetReturnTypeWithState(method); + if (returnNotNull) + { + returnState = returnState.WithNotNullState(); + } + + if (node.Type.IsDynamic() && !node.Method.ReturnType.IsDynamic()) + { + Debug.Assert(!node.Method.ReturnsByRef); + SetDynamicResult(node, returnState); + } + else + { + SetResult(node, returnState, method.ReturnTypeWithAnnotations); + } - SetResult(node, returnState, method.ReturnTypeWithAnnotations); - SetUpdatedSymbol(node, node.Method, method); + SetUpdatedSymbol(node, node.Method, method); + } } private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray results) @@ -9778,7 +9842,7 @@ private void VisitThisOrBaseReference(BoundExpression node) } else { - SetResult(node, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); + SetResultConveringToDynamicIfNecessary(node, left, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); } AdjustSetValue(left, ref rightState); @@ -9788,6 +9852,21 @@ private void VisitThisOrBaseReference(BoundExpression node) return null; } + private void SetResultConveringToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression assignmentTarget, TypeWithState resultType, TypeWithAnnotations lvalueType) + { + Debug.Assert(originalAssignment.Type is not null); + Debug.Assert(assignmentTarget.Type is not null); + + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(originalAssignment, assignmentTarget)) + { + SetDynamicResult(originalAssignment, resultType); + } + else + { + SetResult(originalAssignment, resultType, lvalueType); + } + } + private bool IsPropertyOutputMoreStrictThanInput(PropertySymbol property) { var type = property.TypeWithAnnotations; @@ -10297,7 +10376,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress { var op = node.OperatorKind.Operator(); TypeWithState resultType = (op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement) ? resultOfIncrementType : operandType; - SetResultType(node, resultType); + SetResultConveringToDynamicIfNecessary(node, node.Operand, resultType, resultType.ToTypeWithAnnotations(compilation)); setResult = true; TrackNullableStateForAssignment(node, targetType: operandLvalue, targetSlot: MakeSlot(node.Operand), valueType: resultOfIncrementType); @@ -10365,7 +10444,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress // Handle `[DisallowNull]` on LHS operand (final assignment target). CheckDisallowedNullAssignment(resultTypeWithState, leftArgumentAnnotations, node.Syntax); - SetResultType(node, resultTypeWithState); + SetResultConveringToDynamicIfNecessary(node, node.Left, resultTypeWithState, resultTypeWithState.ToTypeWithAnnotations(compilation)); AdjustSetValue(node.Left, ref resultTypeWithState); Debug.Assert(MakeSlot(node) == -1); @@ -10503,7 +10582,17 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter VisitArguments(node, node.Arguments, node.ArgumentRefKindsOpt, indexer, node.ArgsToParamsOpt, node.DefaultArguments, node.Expanded); var resultType = ApplyUnconditionalAnnotations(indexer.TypeWithAnnotations.ToTypeWithState(), GetRValueAnnotations(indexer)); - SetResult(node, resultType, indexer.TypeWithAnnotations); + + if (node.Type.IsDynamic() && !node.Indexer.Type.IsDynamic()) + { + Debug.Assert(!node.Indexer.ReturnsByRef); + SetDynamicResult(node, resultType); + } + else + { + SetResult(node, resultType, indexer.TypeWithAnnotations); + } + SetUpdatedSymbol(node, node.Indexer, indexer); return null; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index ee4a1091d7b32..9fcab11c2c2f6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -81,14 +81,56 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo break; } - return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, node.Type, used); + var result = MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, left, result, used); + + Debug.Assert(used || result.Type?.IsVoidType() == true || + (left switch { BoundIndexerAccess indexer => indexer.Indexer, BoundPropertyAccess property => property.PropertySymbol, _ => null }) is not PropertySymbol prop || + prop.GetOwnOrInheritedSetMethod() is null); + + Debug.Assert(result.Type?.IsVoidType() == true || TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); + + return result; + } + + private static bool ShouldConvertResultOfAssignmentToDynamic(TypeSymbol? assignmentResultType, BoundExpression target) + { + if (assignmentResultType?.IsDynamic() == true && target is BoundIndexerAccess { Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.Type.IsDynamic()); + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + + return true; + } + + return false; + } + + internal static bool ShouldConvertResultOfAssignmentToDynamic(BoundExpression assignment, BoundExpression target) + { + Debug.Assert(assignment is BoundAssignmentOperator or BoundIncrementOperator or BoundCompoundAssignmentOperator or BoundNullCoalescingAssignmentOperator); + return ShouldConvertResultOfAssignmentToDynamic(assignment.Type, target); + } + + private BoundExpression ConvertResultOfAssignmentToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression originalTarget, BoundExpression result, bool used) + { + Debug.Assert(originalAssignment.Type is not null); + if (used && ShouldConvertResultOfAssignmentToDynamic(originalAssignment, originalTarget)) + { + Debug.Assert(result.Type is not null); + Debug.Assert(!result.Type.IsDynamic()); + result = _factory.Convert(originalAssignment.Type, result); + } + + return result; } /// /// Generates a lowered form of the assignment operator for the given left and right sub-expressions. /// Left and right sub-expressions must be in lowered form. /// - private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, TypeSymbol type, + private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool used, bool isChecked, bool isCompoundAssignment) { switch (rewrittenLeft.Kind) @@ -132,7 +174,7 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio throw ExceptionUtilities.Unreachable(); default: - return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, type: type, used: used); + return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, used: used); } } @@ -168,7 +210,6 @@ private BoundExpression MakeStaticAssignmentOperator( BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool isRef, - TypeSymbol type, bool used) { switch (rewrittenLeft.Kind) @@ -193,7 +234,6 @@ private BoundExpression MakeStaticAssignmentOperator( false, default(ImmutableArray), rewrittenRight, - type, used); } @@ -214,7 +254,6 @@ private BoundExpression MakeStaticAssignmentOperator( indexerAccess.Expanded, indexerAccess.ArgsToParamsOpt, rewrittenRight, - type, used); } @@ -227,7 +266,6 @@ private BoundExpression MakeStaticAssignmentOperator( syntax, rewrittenLeft, rewrittenRight, - type, isRef); } @@ -252,9 +290,8 @@ private BoundExpression MakeStaticAssignmentOperator( sequence.Value, rewrittenRight, isRef, - type, used), - type); + sequence.Type); } goto default; @@ -264,8 +301,7 @@ private BoundExpression MakeStaticAssignmentOperator( return _factory.AssignmentExpression( syntax, rewrittenLeft, - rewrittenRight, - type); + rewrittenRight); } } } @@ -279,7 +315,6 @@ private BoundExpression MakePropertyAssignment( bool expanded, ImmutableArray argsToParamsOpt, BoundExpression rewrittenRight, - TypeSymbol type, bool used) { // Rewrite property assignment into call to setter. @@ -350,7 +385,7 @@ private BoundExpression MakePropertyAssignment( AppendToPossibleNull(argTemps, rhsTemp), ImmutableArray.Create(setterCall), boundRhs, - type); + rhsTemp.Type); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 7e7c8d253ac70..956ff4e6071c1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -410,13 +410,23 @@ BoundExpression visitArgumentsAndFinishRewrite(BoundCall node, BoundExpression? Instrumenter.InterceptCallAndAdjustArguments(ref method, ref rewrittenReceiver, ref rewrittenArguments, ref argRefKindsOpt); } - var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, node.Type, temps.ToImmutableAndFree()); + var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, temps.ToImmutableAndFree()); if (Instrument) { rewrittenCall = Instrumenter.InstrumentCall(node, rewrittenCall); } + if (node.Type.IsDynamic() && !method.ReturnType.IsDynamic()) + { + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(!method.ReturnsByRef); + Debug.Assert(rewrittenCall.Type is not null); + Debug.Assert(!rewrittenCall.Type.IsDynamic()); + Debug.Assert(!rewrittenCall.Type.IsVoidType()); + rewrittenCall = _factory.Convert(node.Type, rewrittenCall); + } + return rewrittenCall; } } @@ -429,7 +439,6 @@ private BoundExpression MakeCall( ImmutableArray rewrittenArguments, ImmutableArray argumentRefKinds, LookupResultKind resultKind, - TypeSymbol type, ImmutableArray temps) { BoundExpression rewrittenBoundCall; @@ -454,7 +463,7 @@ private BoundExpression MakeCall( resultKind, rewrittenArguments[0], rewrittenArguments[1], - type); + method.ReturnType); } else if (node == null) { @@ -472,7 +481,7 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), resultKind: resultKind, - type: type); + type: method.ReturnType); } else { @@ -489,9 +498,11 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), node.ResultKind, - node.Type); + method.ReturnType); } + Debug.Assert(rewrittenBoundCall.Type is not null); + if (!temps.IsDefaultOrEmpty) { return new BoundSequence( @@ -499,13 +510,13 @@ private BoundExpression MakeCall( locals: temps, sideEffects: ImmutableArray.Empty, value: rewrittenBoundCall, - type: type); + type: rewrittenBoundCall.Type); } return rewrittenBoundCall; } - private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments, TypeSymbol type) + private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments) { return MakeCall( node: null, @@ -515,7 +526,6 @@ private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenRe rewrittenArguments: rewrittenArguments, argumentRefKinds: default(ImmutableArray), resultKind: LookupResultKind.Viable, - type: type, temps: default); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index c015096e3b21c..ed3c55336a5d1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -113,6 +113,10 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO temps.Free(); stores.Free(); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Left, result, used); + + Debug.Assert(used || node.Left is not (BoundIndexerAccess { Indexer.RefKind: RefKind.None } or BoundPropertyAccess { PropertySymbol.RefKind: RefKind.None }) || result.Type?.IsVoidType() == true); return result; BoundExpression rewriteAssignment(BoundExpression leftRead) @@ -153,7 +157,8 @@ BoundExpression rewriteAssignment(BoundExpression leftRead) RemovePlaceholderReplacement(node.FinalPlaceholder); } - return MakeAssignmentOperator(syntax, transformedLHS, opFinal, node.Left.Type, used: used, isChecked: isChecked, isCompoundAssignment: true); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.Left.Type, TypeCompareKind.AllIgnoreOptions)); + return MakeAssignmentOperator(syntax, transformedLHS, opFinal, used: used, isChecked: isChecked, isCompoundAssignment: true); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 635043ef290bd..4f6b4f96c378d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -1421,9 +1421,6 @@ private BoundExpression RewriteIntPtrConversion( rewrittenOperand = MakeConversionNode(rewrittenOperand, method.GetParameterType(0), @checked); - var returnType = method.ReturnType; - Debug.Assert((object)returnType != null); - if (_inExpressionLambda) { return BoundConversion.Synthesized(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode: explicitCastInCode, conversionGroupOpt: null, constantValueOpt, rewrittenType); @@ -1433,8 +1430,7 @@ private BoundExpression RewriteIntPtrConversion( syntax: syntax, rewrittenReceiver: null, method: method, - rewrittenArguments: ImmutableArray.Create(rewrittenOperand), - type: returnType); + rewrittenArguments: ImmutableArray.Create(rewrittenOperand)); return MakeConversionNode(rewrittenCall, rewrittenType, @checked, markAsChecked: true); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index dfbe8f214ae22..bb24b2810febb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -18,8 +18,8 @@ internal sealed partial class LocalRewriter { var right = node.Right; Debug.Assert(right.Conversion.Kind == ConversionKind.Deconstruction); - - return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed); + Debug.Assert(node.Type.IsTupleType); + return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed, (NamedTypeSymbol)node.Type); } /// @@ -34,13 +34,12 @@ internal sealed partial class LocalRewriter /// - the conversion phase /// - the assignment phase /// - private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed) + private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed, NamedTypeSymbol assignmentResultTupleType) { var lhsTemps = ArrayBuilder.GetInstance(); var lhsEffects = ArrayBuilder.GetInstance(); ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects); - Debug.Assert(left.Type is { }); - BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed); + BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, right, assignmentResultTupleType, isUsed); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); if (result is null) { @@ -55,8 +54,8 @@ internal sealed partial class LocalRewriter private BoundExpression? RewriteDeconstruction( ArrayBuilder lhsTargets, Conversion conversion, - TypeSymbol leftType, BoundExpression right, + NamedTypeSymbol assignmentResultTupleType, bool isUsed) { if (right.Kind == BoundKind.ConditionalOperator) @@ -66,17 +65,17 @@ internal sealed partial class LocalRewriter return conditional.Update( conditional.IsRef, VisitExpression(conditional.Condition), - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true)!, - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Consequence, assignmentResultTupleType, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Alternative, assignmentResultTupleType, isUsed: true)!, conditional.ConstantValueOpt, - leftType, + assignmentResultTupleType, wasTargetTyped: true, - leftType); + assignmentResultTupleType); } var temps = ArrayBuilder.GetInstance(); var effects = DeconstructionSideEffects.GetInstance(); - BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true); + BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, assignmentResultTupleType, isUsed, inInit: true); reverseAssignmentsToTargetsIfApplicable(); effects.Consolidate(); @@ -213,6 +212,7 @@ static bool canReorderTargetAssignments(ArrayBuilder temps, DeconstructionSideEffects effects, + NamedTypeSymbol assignmentResultTupleType, bool isUsed, bool inInit) { @@ -227,14 +227,19 @@ static bool canReorderTargetAssignments(ArrayBuilder(removeDelegate), - type: clearMethod.ReturnType); + rewrittenArguments: ImmutableArray.Create(removeDelegate)); } else { @@ -163,8 +163,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode syntax: syntax, rewrittenReceiver: null, method: marshalMethod, - rewrittenArguments: marshalArguments, - type: marshalMethod.ReturnType); + rewrittenArguments: marshalArguments); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index 2009b7fb09125..d69554e9b960d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -91,7 +91,6 @@ private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftO node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, - node.Type, node, isLeftOfAssignment); } @@ -106,17 +105,22 @@ private BoundExpression MakeIndexerAccess( bool expanded, ImmutableArray argsToParamsOpt, BitVector defaultArguments, - TypeSymbol type, - BoundIndexerAccess? oldNodeOpt, + BoundExpression originalIndexerAccessOrObjectInitializerMember, bool isLeftOfAssignment) { + Debug.Assert(originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess or BoundObjectInitializerMember); + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type is not null); + if (isLeftOfAssignment && indexer.RefKind == RefKind.None) { + TypeSymbol type = indexer.Type; + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type.Equals(type, TypeCompareKind.ConsiderEverything)); + // This is an indexer set access. We return a BoundIndexerAccess node here. // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. - return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : + return originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess indexerAccess ? + indexerAccess.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : new BoundIndexerAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type); } else @@ -145,6 +149,16 @@ private BoundExpression MakeIndexerAccess( BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentRefKindsOpt, getMethod); + if (originalIndexerAccessOrObjectInitializerMember.Type.IsDynamic() == true && !indexer.Type.IsDynamic()) + { + Debug.Assert(call.Type is not null); + Debug.Assert(!call.Type.IsDynamic()); + Debug.Assert(!getMethod.ReturnsByRef); + call = _factory.Convert(originalIndexerAccessOrObjectInitializerMember.Type, call); + } + + Debug.Assert(call.Type is not null); + if (temps.Count == 0) { temps.Free(); @@ -157,7 +171,7 @@ private BoundExpression MakeIndexerAccess( temps.ToImmutableAndFree(), ImmutableArray.Empty, call, - type); + call.Type); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs index b2d42a9a8597e..7e24d7df4caa4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs @@ -63,7 +63,6 @@ internal sealed partial class LocalRewriter localSymbol.Type ), rewrittenInitializer, - localSymbol.Type, localSymbol.IsRef), hasErrors); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs index 851da6ffc4147..6b3b62aa0bc01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs @@ -25,10 +25,12 @@ public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalesc var lhsRead = MakeRValue(transformedLHS); BoundExpression loweredRight = VisitExpression(node.RightOperand); - return node.IsNullableValueTypeAssignment ? + var result = node.IsNullableValueTypeAssignment ? rewriteNullCoalescingAssignmentForValueType() : rewriteNullCoalscingAssignmentStandard(); + return ConvertResultOfAssignmentToDynamicIfNecessary(node, node.LeftOperand, result, used: true); + BoundExpression rewriteNullCoalscingAssignmentStandard() { // Now that LHS is transformed with temporaries, we rewrite this node into a coalesce expression: @@ -38,7 +40,8 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() // isCompoundAssignment is only used for dynamic scenarios, and we want those scenarios to treat this like a standard assignment. // See CodeGenNullCoalescingAssignmentTests.CoalescingAssignment_DynamicRuntimeCastFailure, which will fail if // isCompoundAssignment is set to true. It will fail to throw a runtime binder cast exception. - BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); + BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); // lhsRead ?? (transformedLHS = loweredRight) var leftPlaceholder = new BoundValuePlaceholder(lhsRead.Syntax, lhsRead.Type); @@ -60,7 +63,6 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() BoundExpression rewriteNullCoalescingAssignmentForValueType() { Debug.Assert(node.LeftOperand.Type.IsNullableType()); - Debug.Assert(node.Type.Equals(node.RightOperand.Type)); // We lower the expression to this form: // @@ -107,17 +109,17 @@ BoundExpression rewriteNullCoalescingAssignmentForValueType() temps.Add(tmp.LocalSymbol); // tmp = loweredRight; - var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, node.Type, used: true, isChecked: false, isCompoundAssignment: false); + var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); Debug.Assert(transformedLHS.Type.GetNullableUnderlyingType().Equals(tmp.Type.StrippedType(), TypeCompareKind.AllIgnoreOptions)); // transformedLhs = tmp; + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); var transformedLhsAssignment = MakeAssignmentOperator( node.Syntax, transformedLHS, MakeConversionNode(tmp, transformedLHS.Type, @checked: false, markAsChecked: true), - node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index 5ca410e45cde8..8583c6cf6329a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -206,7 +206,7 @@ private BoundExpression MakeDynamicCollectionInitializer(BoundExpression rewritt Instrumenter.InterceptCallAndAdjustArguments(ref addMethod, ref rewrittenReceiver, ref rewrittenArguments, ref argumentRefKindsOpt); } - return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, temps.ToImmutableAndFree()); } private BoundExpression VisitObjectInitializerMember(BoundObjectInitializerMember node, ref BoundExpression rewrittenReceiver, ArrayBuilder sideEffects, ref ArrayBuilder? temps) @@ -363,7 +363,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, assignment.Type, used: false)); + Debug.Assert(assignment.Type.IsDynamic() || TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, used: false)); return; } } @@ -434,7 +435,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -466,7 +468,8 @@ private void AddObjectInitializer( { // Rewrite as simple assignment. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -499,7 +502,8 @@ private void AddObjectInitializer( if (!isRhsNestedInitializer) { var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, used: false)); return; } @@ -684,8 +688,7 @@ private BoundExpression MakeObjectInitializerMemberAccess( rewrittenLeft.Expanded, rewrittenLeft.ArgsToParamsOpt, rewrittenLeft.DefaultArguments, - type: propertySymbol.Type, - oldNodeOpt: null, + originalIndexerAccessOrObjectInitializerMember: rewrittenLeft, isLeftOfAssignment: !isRhsNestedInitializer); } else diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 5e3384761eba7..00d6f2d7854b0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -438,7 +438,8 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamic); TypeSymbol? operandType = transformedLHS.Type; //type of the variable being incremented Debug.Assert(operandType is { }); - Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2)); + Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2) || + ShouldConvertResultOfAssignmentToDynamic(node, node.Operand)); LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType); tempSymbols.Add(tempSymbol); @@ -452,7 +453,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // prefix: (X)(T.Increment((T)operand))) // postfix: (X)(T.Increment((T)temp))) - var newValue = MakeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); + var newValue = makeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); // there are two strategies for completing the rewrite. // The reason is that indirect assignments read the target of the assignment before evaluating @@ -472,122 +473,131 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // In a case of the non-byref operand we use a single-sequence strategy as it results in shorter // overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case) // - if (IsIndirectOrInstanceField(transformedLHS)) + BoundExpression result; + + if (isIndirectOrInstanceField(transformedLHS)) { - return RewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } else { - return RewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } - } - - private static bool IsIndirectOrInstanceField(BoundExpression expression) - { - switch (expression.Kind) - { - case BoundKind.Local: - return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - case BoundKind.Parameter: - Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); - return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Operand, result, used: true); + Debug.Assert(TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); - case BoundKind.FieldAccess: - return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; - } + return result; - return false; - } + static bool isIndirectOrInstanceField(BoundExpression expression) + { + switch (expression.Kind) + { + case BoundKind.Local: + return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithNotRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression transformedLHS, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; - // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); - ImmutableArray assignments = ImmutableArray.Create( - MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), operandType, used: false, isChecked: isChecked, isCompoundAssignment: false), - MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false)); - - // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) - // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), - value: boundTemp, - type: operandType); - } + case BoundKind.Parameter: + Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); + return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression operand, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - var tempValue = isPrefix ? newValue : MakeRValue(operand); - Debug.Assert(tempValue.Type is { }); - var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); + case BoundKind.FieldAccess: + return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; + } - var operandValue = isPrefix ? boundTemp : newValue; - var tempAssignedAndOperandValue = new BoundSequence( - syntax, - ImmutableArray.Empty, - ImmutableArray.Create(tempAssignment), - operandValue, - tempValue.Type); - - // prefix: operand = Seq{temp = (T)(operand + 1); temp;} - // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} - BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); - - // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} - // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} - tempInitializers.Add(operandAssignment); - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree(), - value: boundTemp, - type: operandType); - } + return false; + } - private BoundExpression MakeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) - { - if (node.OperatorKind.IsDynamic()) + BoundExpression rewriteWithNotRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression transformedLHS, + BoundExpression boundTemp, + BoundExpression newValue) { - return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + Debug.Assert(boundTemp.Type is not null); + + // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; + // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); + ImmutableArray assignments = ImmutableArray.Create( + MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), used: false, isChecked: isChecked, isCompoundAssignment: false), + MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, used: false, isChecked: isChecked, isCompoundAssignment: false)); + + // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) + // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), + value: boundTemp, + type: boundTemp.Type); } - BoundExpression result; - if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + BoundExpression rewriteWithRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression operand, + BoundExpression boundTemp, + BoundExpression newValue) { - result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + Debug.Assert(boundTemp.Type is not null); + + var tempValue = isPrefix ? newValue : MakeRValue(operand); + Debug.Assert(tempValue.Type is { }); + var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + var operandValue = isPrefix ? boundTemp : newValue; + var tempAssignedAndOperandValue = new BoundSequence( + syntax, + ImmutableArray.Empty, + ImmutableArray.Create(tempAssignment), + operandValue, + tempValue.Type); + + // prefix: operand = Seq{temp = (T)(operand + 1); temp;} + // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} + BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} + // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} + tempInitializers.Add(operandAssignment); + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree(), + value: boundTemp, + type: boundTemp.Type); } - else + + BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { - result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); - } + if (node.OperatorKind.IsDynamic()) + { + return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + } - // Generate the conversion back to the type of the original expression. + BoundExpression result; + if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + { + result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + } + else + { + result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); + } - // (X)(short)((int)(short)x + 1) - result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + // Generate the conversion back to the type of the original expression. - return result; + // (X)(short)((int)(short)x + 1) + result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + + return result; + } } private BoundExpression ApplyConversionIfNotIdentity(BoundExpression? conversion, BoundValuePlaceholder? placeholder, BoundExpression replacement) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 4997351f94f68..249c7b23c0f1e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -532,7 +532,7 @@ private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo method ref temps, invokedAsExtensionMethod: method.IsExtensionMethod); - return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, method.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, temps.ToImmutableAndFree()); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index b94eddbaea342..ec3d3a6db7114 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -469,20 +469,20 @@ public BoundExpressionStatement ExpressionStatement(BoundExpression expr) /// public BoundExpression AssignmentExpression(BoundExpression left, BoundExpression right, bool isRef = false) { - Debug.Assert(left.Type is { } && right.Type is { } && - (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || - StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || - right.Type.IsErrorType() || left.Type.IsErrorType())); - - return AssignmentExpression(Syntax, left, right, left.Type, isRef: isRef, wasCompilerGenerated: true); + return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true); } /// /// Creates a general assignment that might be instrumented. /// - public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, TypeSymbol type, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) + public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) { - var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; + Debug.Assert(left.Type is { } && right.Type is { } && + (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || + StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || + right.Type.IsErrorType() || left.Type.IsErrorType())); + + var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, left.Type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; return (InstrumentationState?.IsSuppressed == false && left is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.UserDefined } or BoundParameter) ? InstrumentationState.Instrumenter.InstrumentUserDefinedLocalAssignment(assignment) : diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs index ebde2d102e21d..a382338ea9d86 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs @@ -15000,6 +15000,42 @@ static void M(bool result) VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72931")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void ObjectCreationFlow_75_CollectionInitializerError() + { + string source = @" +public class C +{ + public static void Main() + /**/{ + int d = 1; + var c = new C() { [d] = {2} }; + }/**/ + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + } +} + +class C2 +{ +} +"; + var expectedDiagnostics = new[] { + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + }; + + string expectedFlowGraph = @" +"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + [Fact] public void ObjectCreationExpression_NoNewConstraint() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index c965d1742584e..b9cd381d376f6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs @@ -13,11 +13,32 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { - public partial class SyntaxBinderTests + public partial class DynamicTests : CompilingTestBase { + private static void TestTypes(string source) + { + SyntaxBinderTests.TestTypes(source); + } + + private static void TestOperatorKinds(string source) + { + SyntaxBinderTests.TestOperatorKinds(source); + } + + private static void TestDynamicMemberAccessCore(string source) + { + SyntaxBinderTests.TestDynamicMemberAccessCore(source); + } + + private static void TestCompoundAssignment(string source) + { + SyntaxBinderTests.TestCompoundAssignment(source); + } + #region Conversions [Fact] @@ -3013,7 +3034,7 @@ public C1(int x){} public C1(long x){} } "; - CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)) }).VerifyDiagnostics( + CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.RegularPreview) }).VerifyDiagnostics( // (43,55): warning CS1981: Using 'is' to test compatibility with 'dynamic' is essentially identical to testing compatibility with 'Object' and will succeed for all non-null values // Expression> e18 = x => d is dynamic; // ok, warning @@ -3079,6 +3100,76 @@ public C1(long x){} // Expression> e24 = x => new C1(x); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "new C1(x)").WithLocation(49, 55) ); + + CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)) }).VerifyDiagnostics( + + // (43,55): warning CS1981: Using 'is' to test compatibility with 'dynamic' is essentially identical to testing compatibility with 'Object' and will succeed for all non-null values + // Expression> e18 = x => d is dynamic; // ok, warning + Diagnostic(ErrorCode.WRN_IsDynamicIsConfusing, "d is dynamic").WithArguments("is", "dynamic", "Object").WithLocation(43, 55), + // (46,59): error CS8382: Invalid object creation + // Expression> e21 = x => new dynamic(); + Diagnostic(ErrorCode.ERR_InvalidObjectCreation, "dynamic").WithLocation(46, 59), + // (25,52): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e0 = () => new C { P = d }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(25, 52), + // (27,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "X").WithLocation(27, 54), + // (27,60): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Y").WithLocation(27, 60), + // (27,69): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Z").WithLocation(27, 69), + // (28,44): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e3 = () => new C() { { d }, { d, d, d } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d }").WithLocation(28, 44), + // (28,51): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e3 = () => new C() { { d }, { d, d, d } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d, d, d }").WithLocation(28, 51), + // (29,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e4 = x => x.goo(); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo()").WithLocation(29, 54), + // (29,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e4 = x => x.goo(); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo").WithLocation(29, 54), + // (30,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e5 = x => x[1]; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x[1]").WithLocation(30, 54), + // (31,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e6 = x => x.y.z; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.y.z").WithLocation(31, 54), + // (31,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e6 = x => x.y.z; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.y").WithLocation(31, 54), + // (32,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e7 = x => x + 1; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x + 1").WithLocation(32, 54), + // (33,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e8 = x => -x; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "-x").WithLocation(33, 54), + // (34,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e9 = x => f(d); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d)").WithLocation(34, 54), + // (36,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e11 = x => f((dynamic)1); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f((dynamic)1)").WithLocation(36, 55), + // (37,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e12 = x => f(d ?? null); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d ?? null)").WithLocation(37, 55), + // (38,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e13 = x => d ? 1 : 2; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(38, 55), + // (39,56): error CS1989: Async lambda expressions cannot be converted to expression trees + // Expression>> e14 = async x => await d; + Diagnostic(ErrorCode.ERR_BadAsyncExpressionTree, "async x => await d").WithLocation(39, 56), + // (47,84): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e22 = x => from a in new[] { d } select a + 1; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "a + 1").WithLocation(47, 84), + // (49,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e24 = x => new C1(x); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "new C1(x)").WithLocation(49, 55) + ); } [Fact, WorkItem(578401, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578401")] @@ -4399,13 +4490,24 @@ static void Main() } "; - var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); CompileAndVerify(comp, expectedOutput: @" True True ").VerifyDiagnostics(); + + comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + + comp.VerifyEmitDiagnostics( + // (10,15): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 15), + // (10,28): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 28) + ); } [WorkItem(22813, "https://github.com/dotnet/roslyn/issues/22813")] @@ -4545,5 +4647,6904 @@ class C2 : C1 CompileAndVerify(comp, expectedOutput: "int").VerifyDiagnostics(); } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_01(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_02(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("dynamic I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + dynamic Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => i1.Test(""name"", value)").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_Extension(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"new C().Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(name: ""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = Test(&name, d); + System.Console.Write(result); + } + + static int Test(string* name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(&name, d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 C.Test(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params System.Collections.Generic.List list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_VoidReturning() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var a = Test1(d); + } + + static void Test1(int x) {} +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable + // var a = Test1(d); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + Test1(d)++; + var a = Test1(d); + System.Console.WriteLine(a); + } + + static int _test1 = 0; + static ref int Test1(int x) => ref _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + _test1 = &v; + + dynamic d = 1; + (*Test1(d))++; + var a = Test1(d); + System.Console.WriteLine(*a); + } + + static int* _test1; + static int* Test1(int x) => _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_LocalFunction([CombinatorialValues(0, 12, 13)] int version) + { + var parseOptions = version switch { 12 => TestOptions.Regular12, 13 => TestOptions.RegularNext, _ => TestOptions.RegularPreview }; + + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var e = test4(d); + System.Console.WriteLine(e); + + static int test4(int x) => x; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "e").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 e", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Delegate(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(name: ""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value) => 123; + delegate int D(string name, object value); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = Test(&name, d); + System.Console.Write(result); + } + + static D Test = (string* name, object value) => 123; + delegate int D(string* name, object value); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(&name, d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 C.D.Invoke(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params System.Collections.Generic.List list) => 123; + delegate int D(string name, object value, params System.Collections.Generic.List list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_VoidReturning_Delegate() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var a = Test1(d); + } + + static D Test1 = null; +} + +delegate void D(int x); +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable + // var a = Test1(d); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Delegate() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + Test1(d)++; + var a = Test1(d); + System.Console.WriteLine(a); + } + + static int _test1 = 0; + static D Test1 = (int x) => ref _test1; + + delegate ref int D(int x); +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion_Delegate() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + _test1 = &v; + + dynamic d = 1; + (*Test1(d))++; + var a = Test1(d); + System.Console.WriteLine(*a); + } + + static int* _test1; + static D Test1 = (int x) => _test1; + delegate int* D(int x); +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Property_01(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Property_02(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_03() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + dynamic this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => i1.get_Item(""name"", value)").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[name: ""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = new C()[&name, d]; + System.Console.Write(result); + } + + int this[string* name, object value] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String* name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params System.Collections.Generic.List list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Collections.Generic.List list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + c[d]++; + var a = c[d]; + System.Console.WriteLine(a); + } + + int _test1 = 0; + ref int this[int x] => ref _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + TypeInfo typeInfo; + + foreach (var elementAccess in tree.GetRoot().DescendantNodes().OfType()) + { + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + } + + var increment = tree.GetRoot().DescendantNodes().OfType().Single(); + typeInfo = model.GetTypeInfo(increment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion_Property() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + var c = new C(); + c._test1 = &v; + + dynamic d = 1; + (*c[d])++; + var a = c[d]; + System.Console.WriteLine(*a); + } + + int* _test1; + int* this[int x] => _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + TypeInfo typeInfo; + + foreach (var elementAccess in tree.GetRoot().DescendantNodes().OfType()) + { + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32* C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32*", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32*", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + } + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic right = 2; + var a = c[d] = right; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic? right = (int?)null; + var a = c[d] = right; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + object o = null; + var c = new C(); + var a = c[d] = o; + System.Console.Write(a); + } + + System.IO.Stream this[int x] + { + get => null; + set {} + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.IO.Stream C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.IO.Stream", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.IO.Stream", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Object", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (9,24): error CS0266: Cannot implicitly convert type 'object' to 'System.IO.Stream'. An explicit conversion exists (are you missing a cast?) + // var a = c[d] = o; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "o").WithArguments("object", "System.IO.Stream").WithLocation(9, 24) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (10,17): warning CS0458: The result of the expression is always 'null' of type 'dynamic' + // var a = c[d] += (int?)null; + Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("dynamic").WithLocation(10, 17), + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72906")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullable analysis behavior is consistent with how dynamic operators are analyzed. + // See https://github.com/dotnet/roslyn/issues/72906, for example. + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72906")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += d; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Addition(System.Int32 left, dynamic right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1 1").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + dynamic? right = null; + var c = new C(); + var a = c[d] += right; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullable analysis behavior is consistent with how dynamic operators are analyzed. + // See https://github.com/dotnet/roslyn/issues/72906, for example. + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_OperatorIsBoundStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + C2 right = new C2(); + var a = c[d] += right; + Print(a); + } + + C2 this[int x] + { + get => new C2(); + set {} + } + + static void Print(dynamic b) + { + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + Assert.Null(symbolInfo.Symbol); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + Assert.True(operation.Type.IsErrorType()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (9,17): error CS0019: Operator '+=' cannot be applied to operands of type 'C2' and 'C2' + // var a = c[d] += right; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] += right").WithArguments("+=", "C2", "C2").WithLocation(9, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_CompoundAssignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (10,17): warning CS0458: The result of the expression is always 'null' of type 'int?' + // var a = c[d] += (int?)null; + Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("int?").WithLocation(10, 17), + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "2 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + int? _test1 = 2; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "2 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + int? _test1 = 2; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_OperatorIsBoundStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + + C2 this[int x] + { + get => new C2(); + set {} + } + + static void Print(dynamic b) + { + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.Equal(CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.Equal(typeInfo.Type, typeInfo.ConvertedType); + symbolInfo = model.GetSymbolInfo(assignment); + Assert.Null(symbolInfo.Symbol); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + comp.VerifyDiagnostics( + // (8,17): error CS0023: Operator '++' cannot be applied to operand of type 'C2' + // var a = c[d]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "c[d]++").WithArguments("++", "C2").WithLocation(8, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PrefixIncrement_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PrefixIncrement_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_PrefixIncrement() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + string this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (string?)null; + Print(a); + } + + string? _test1 = null; + string? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int? _test1 = null; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (int?)null; + Print(a); + } + + int? _test1 = null; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (string?)null; + Print(a); + } + + string? _test1 = null; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_04() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic right = ""2""; + var a = c[d] ??= right; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + string this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic? right = null; + var a = c[d] ??= right; + Print(a); + } + + string? _test1 = null; + string? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72912")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_RightSideIsConvertedStatically() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + C2? _test1 = null; + C2? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("C2? a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + // The unexpected nullability warning is pre-existing condition - https://github.com/dotnet/roslyn/issues/72912 + comp.VerifyDiagnostics( + // (10,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'string' + // var a = c[d] ??= "2"; + Diagnostic(ErrorCode.ERR_BadBinaryOps, @"c[d] ??= ""2""").WithArguments("??=", "C2", "string").WithLocation(10, 17), + // (10,26): warning CS8619: Nullability of reference types in value of type 'string' doesn't match target type 'C2'. + // var a = c[d] ??= "2"; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, @"""2""").WithArguments("string", "C2").WithLocation(10, 26) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_Error() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= new C2(); + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + C2 _test1; + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} + +struct C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (8,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'C2' + // var a = c[d] ??= new C2(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] ??= new C2()").WithArguments("??=", "C2", "C2").WithLocation(8, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_ConditionalAssignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int? _test1 = null; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32? C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Print(expr); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Print(expr); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_03() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + dynamic v = 2; + var c = new C() { [d] = v }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Print(expr); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,70): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 70), + // (9,71): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 71), + // (9,76): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "v").WithLocation(9, 76) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = ""2"" }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (7,33): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var c = new C() { [d] = "2" }; + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_Assignment() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + // IInvalidOperation is pre-existing condition - https://github.com/dotnet/roslyn/issues/72916 + var propertyRef = (IInvalidOperation)model.GetOperation(elementAccess); + //var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + //AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_ObjectInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref C2 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,35): error CS0117: 'C2' does not contain a definition for 'F' + // var c = new C() { [d] = { F = 2 } }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "F").WithArguments("C2", "F").WithLocation(7, 35) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [ConditionalFact(typeof(NoIOperationValidation))] // IOperation validation is suppressed due to https://github.com/dotnet/roslyn/issues/72931 + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_CollectionInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Collections.Generic.List C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Collections.Generic.List", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic)2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (""2"", 123); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,30): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = ("2", 123); + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(8, 30) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Tuple() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72913")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + comp3.VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72914")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp3.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int?'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int?").WithLocation(10, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out string x, out int y) + { + (x, y) = (""2"", 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c[d]").WithArguments("string", "int").WithLocation(8, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Method() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = ((2, 123), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + right = (TupleExpressionSyntax)right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = (new C2(), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(C2, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(C2, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Conditional() + { + string source = @" +public class C +{ + public static void Main() + { + Test(true); + System.Console.Write("" ""); + Test(false); + } + + static void Test(bool b) + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = b ? (2, 123) : (3, 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2 (3, 124) 3").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_01() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_02() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_Extension_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var model = comp1.GetSemanticModel(tree); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal(OperationKind.Invalid, model.GetOperation(call).Kind); + + comp1.VerifyDiagnostics( + // (7,22): error CS1973: 'C' has no applicable method named 'Test' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. + // var result = new C().Test("name", d); + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, @"new C().Test(""name"", d)").WithArguments("C", "Test").WithLocation(7, 22) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Delegate_CSharp12() + { + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_01() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_02() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index b31d4f8291d41..b4990d8031bfd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -3192,7 +3192,7 @@ static void M() TestOperatorKinds(code); } - private void TestBoundTree(string source, System.Func>, IEnumerable> query) + private static void TestBoundTree(string source, System.Func>, IEnumerable> query) { // The mechanism of this test is: we build the bound tree for the code passed in and then extract // from it the nodes that describe the operators. We then compare the description of @@ -3217,7 +3217,7 @@ private void TestBoundTree(string source, System.Func from edge in edges @@ -3260,7 +3260,7 @@ select string.Join(" ", from child in node.Children }))); } - private void TestTypes(string source) + internal static void TestTypes(string source) { TestBoundTree(source, edges => from edge in edges @@ -3289,7 +3289,7 @@ private static string FormatTypeArgumentList(ImmutableArray return s + ">"; } - private void TestDynamicMemberAccessCore(string source) + internal static void TestDynamicMemberAccessCore(string source) { TestBoundTree(source, edges => from edge in edges diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs index 882913c850da3..2e233219a3fc0 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs @@ -225,7 +225,7 @@ public void M(dynamic d) "; var semanticInfo = GetSemanticInfoForTest(sourceCode1); - Assert.Equal("C", semanticInfo.Type.ToTestDisplayString()); + Assert.True(semanticInfo.Type.IsDynamic()); Assert.Equal("C C.Create(System.Int32 arg)", semanticInfo.Symbol.ToTestDisplayString()); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); @@ -548,8 +548,8 @@ public int this[int a] "; var semanticInfo = GetSemanticInfoForTest(sourceCode); - Assert.False(semanticInfo.Type.IsDynamic()); - Assert.False(semanticInfo.ConvertedType.IsDynamic()); + Assert.True(semanticInfo.Type.IsDynamic()); + Assert.True(semanticInfo.ConvertedType.IsDynamic()); Assert.Equal(ConversionKind.Identity, semanticInfo.ImplicitConversion.Kind); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); From aa8aab40f7b376ef5b7e379eac8d9657ea2c5208 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 17 Apr 2024 09:25:00 +0200 Subject: [PATCH 0599/1047] Replace deprecated pool image (#73043) --- azure-pipelines-official.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index bd33388a7258b..ec131688b0f32 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -85,7 +85,7 @@ extends: sdl: sourceAnalysisPool: name: NetCore1ESPool-Svc-Internal - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows sbom: enabled: false From c3aaef8aee7ddb43a7ae756f7ec67e63450de211 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 17 Apr 2024 10:54:14 -0500 Subject: [PATCH 0600/1047] Use frozen partial semantics for snippet evaluation Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2021886 --- .../Snippets/SnippetFunctions/AbstractSnippetFunction.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs index 3727ab638e2b5..61aba67eadf6e 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,9 +27,9 @@ public AbstractSnippetFunction(SnippetExpansionClient snippetExpansionClient, IT _threadingContext = threadingContext; } - protected bool TryGetDocument(out Document document) + protected bool TryGetDocument([NotNullWhen(true)] out Document? document) { - document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()?.WithFrozenPartialSemantics(CancellationToken.None); return document != null; } From f5565d6aa596f404e50ae1f4220ad31e18a074f4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 10:11:40 -0700 Subject: [PATCH 0601/1047] Acquire semantic model from Document instead of making a fresh one --- .../ObsoleteSymbol/AbstractObsoleteSymbolService.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs b/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs index d3349b8b75c30..ed9a0f67a528a 100644 --- a/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs +++ b/src/Workspaces/Core/Portable/ObsoleteSymbol/AbstractObsoleteSymbolService.cs @@ -31,7 +31,7 @@ public async Task> GetLocationsAsync(Document document, { var syntaxFacts = document.GetRequiredLanguageService(); - var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + 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 @@ -39,8 +39,6 @@ public async Task> GetLocationsAsync(Document document, ArrayBuilder? result = null; try { - var semanticModel = compilation.GetSemanticModel(root.SyntaxTree); - foreach (var span in textSpans) { Recurse(span, semanticModel); @@ -68,11 +66,8 @@ void Recurse(TextSpan span, SemanticModel semanticModel) stack.Add(root.FindNode(span)); // Use a stack so we don't blow out the stack with recursion. - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - var current = stack.Last(); - stack.RemoveLast(); - if (current.Span.IntersectsWith(span)) { var tokenFromNode = ProcessNode(semanticModel, current); From 82fbe32d58a469c90f3973bd5f5c23d738fb7a0e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:02:12 -0700 Subject: [PATCH 0602/1047] Pass along cancellation token in snippets --- .../AbstractSnippetFunction.cs | 19 ++++++------------- .../SnippetFunctionClassName.cs | 11 ++--------- .../SnippetFunctionGenerateSwitchCases.cs | 7 ++----- .../SnippetFunctionSimpleTypeName.cs | 9 ++------- 4 files changed, 12 insertions(+), 34 deletions(-) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs index 61aba67eadf6e..d7145bc6db4ea 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs @@ -27,18 +27,13 @@ public AbstractSnippetFunction(SnippetExpansionClient snippetExpansionClient, IT _threadingContext = threadingContext; } - protected bool TryGetDocument([NotNullWhen(true)] out Document? document) - { - document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()?.WithFrozenPartialSemantics(CancellationToken.None); - return document != null; - } + protected Document? GetDocument(CancellationToken cancellationToken) + => _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()?.WithFrozenPartialSemantics(cancellationToken); private int GetDefaultValue(CancellationToken cancellationToken, out string value, out int hasDefaultValue) { - var (ExitCode, Value, HasDefaultValue) = _threadingContext.JoinableTaskFactory.Run(() => GetDefaultValueAsync(cancellationToken)); - value = Value; - hasDefaultValue = HasDefaultValue; - return ExitCode; + (var exitCode, value, hasDefaultValue) = _threadingContext.JoinableTaskFactory.Run(() => GetDefaultValueAsync(cancellationToken)); + return exitCode; } protected virtual Task<(int ExitCode, string Value, int HasDefaultValue)> GetDefaultValueAsync(CancellationToken cancellationToken) @@ -48,10 +43,8 @@ private int GetDefaultValue(CancellationToken cancellationToken, out string valu private int GetCurrentValue(CancellationToken cancellationToken, out string value, out int hasCurrentValue) { - var (ExitCode, Value, HasCurrentValue) = _threadingContext.JoinableTaskFactory.Run(() => GetCurrentValueAsync(cancellationToken)); - value = Value; - hasCurrentValue = HasCurrentValue; - return ExitCode; + (var exitCode, value, hasCurrentValue) = _threadingContext.JoinableTaskFactory.Run(() => GetCurrentValueAsync(cancellationToken)); + return exitCode; } protected virtual Task<(int ExitCode, string Value, int HasCurrentValue)> GetCurrentValueAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs index a732df3a371ef..015dd6d99bc9e 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs @@ -28,29 +28,22 @@ public SnippetFunctionClassName(SnippetExpansionClient snippetExpansionClient, I { var hasDefaultValue = 0; var value = string.Empty; - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.E_FAIL, value, hasDefaultValue); - } var surfaceBufferFieldSpan = new VsTextSpan[1]; if (snippetExpansionClient.ExpansionSession.GetFieldSpan(FieldName, surfaceBufferFieldSpan) != VSConstants.S_OK) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } if (!snippetExpansionClient.TryGetSubjectBufferSpan(surfaceBufferFieldSpan[0], out var subjectBufferFieldSpan)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } var snippetFunctionService = document.Project.GetRequiredLanguageService(); value = await snippetFunctionService.GetContainingClassNameAsync(document, subjectBufferFieldSpan.Start.Position, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(value)) - { hasDefaultValue = 1; - } return (VSConstants.S_OK, value, hasDefaultValue); } diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs index 6044dce6fe387..526e0a7091825 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs @@ -41,10 +41,9 @@ protected override int FieldChanged(string field, out int requeryFunction) protected override async Task<(int ExitCode, string Value, int HasCurrentValue)> GetCurrentValueAsync(CancellationToken cancellationToken) { - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.S_OK, string.Empty, HasCurrentValue: 0); - } // If the switch expression is invalid, still show the default case var hasCurrentValue = 1; @@ -60,9 +59,7 @@ protected override int FieldChanged(string field, out int requeryFunction) var value = await snippetFunctionService.GetSwitchExpansionAsync(document, caseGenerationSpan.Value, switchExpressionSpan.Value, simplifierOptions, cancellationToken).ConfigureAwait(false); if (value == null) - { return (VSConstants.S_OK, snippetFunctionService.SwitchDefaultCaseForm, hasCurrentValue); - } return (VSConstants.S_OK, value, hasCurrentValue); } diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs index 81ce994883a1e..77665d5efd398 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs @@ -35,23 +35,18 @@ public SnippetFunctionSimpleTypeName( { var value = _fullyQualifiedName; var hasDefaultValue = 1; - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.E_FAIL, value, hasDefaultValue); - } if (!TryGetFieldSpan(out var fieldSpan)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } var simplifierOptions = await document.GetSimplifierOptionsAsync(snippetExpansionClient.EditorOptionsService.GlobalOptions, cancellationToken).ConfigureAwait(false); var simplifiedTypeName = await SnippetFunctionService.GetSimplifiedTypeNameAsync(document, fieldSpan.Value, _fullyQualifiedName, simplifierOptions, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(simplifiedTypeName)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } return (VSConstants.S_OK, simplifiedTypeName!, hasDefaultValue); } From e2059ce9a3566bae41d6d56d8ccc4b084e9e6702 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:33:33 -0700 Subject: [PATCH 0603/1047] Add initial caching logic --- .../Microsoft.CodeAnalysis.Workspaces.csproj | 4 +- .../DefaultDocumentTrackingService.cs | 13 +--- .../IDocumentTrackingService.cs | 0 .../IDocumentTrackingServiceExtensions.cs | 0 .../Portable/Workspace/Solution/Document.cs | 74 +++++++++---------- .../Workspace_SemanticModelCaching.cs | 50 +++++++++++++ 6 files changed, 91 insertions(+), 50 deletions(-) rename src/{Features/Core/Portable/SolutionCrawler => Workspaces/Core/Portable/Workspace/DocumentTracking}/DefaultDocumentTrackingService.cs (75%) rename src/{Features/Core/Portable/SolutionCrawler => Workspaces/Core/Portable/Workspace/DocumentTracking}/IDocumentTrackingService.cs (100%) rename src/{Features/Core/Portable/SolutionCrawler => Workspaces/Core/Portable/Workspace/DocumentTracking}/IDocumentTrackingServiceExtensions.cs (100%) create mode 100644 src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 3c1b1a4561ae0..2ef8b99744fab 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -105,7 +105,7 @@ - + @@ -131,7 +131,7 @@ - + diff --git a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs similarity index 75% rename from src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index 75022a16733cf..c070a4f5ea9a1 100644 --- a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -9,16 +9,11 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler; -[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default)] -[Shared] -internal sealed class DefaultDocumentTrackingService : IDocumentTrackingService +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultDocumentTrackingService() - { - } - public bool SupportsDocumentTracking => false; public event EventHandler ActiveDocumentChanged { add { } remove { } } diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs similarity index 100% rename from src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs similarity index 100% rename from src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 7f2bffd0dd681..8f96644bb5ca3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -326,60 +326,56 @@ internal async ValueTask GetRequiredNullableDisabledSemanticModel /// private async Task GetSemanticModelHelperAsync(bool disableNullableAnalysis, CancellationToken cancellationToken) { - try - { - if (!this.SupportsSemanticModel) - { - return null; - } + if (!this.SupportsSemanticModel) + return null; - SemanticModel? semanticModel; - if (disableNullableAnalysis) + var semanticModel = await GetSemanticModelWorkerAsync().ConfigureAwait(false); + this.Project.Solution.Workspace.OnSemanticModelObtained(this.Id, semanticModel); + return semanticModel; + + async Task GetSemanticModelWorkerAsync() + { + try { - if (this.TryGetNullableDisabledSemanticModel(out semanticModel)) + if (disableNullableAnalysis) { - return semanticModel; + if (this.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; } - } - else - { - if (this.TryGetSemanticModel(out semanticModel)) + else { - return semanticModel; + if (this.TryGetSemanticModel(out var semanticModel)) + return semanticModel; } - } - var syntaxTree = await this.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var compilation = await this.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var syntaxTree = await this.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var compilation = await this.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); #pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API - var result = compilation.GetSemanticModel(syntaxTree, disableNullableAnalysis ? SemanticModelOptions.DisableNullableAnalysis : SemanticModelOptions.None); + var result = compilation.GetSemanticModel(syntaxTree, disableNullableAnalysis ? SemanticModelOptions.DisableNullableAnalysis : SemanticModelOptions.None); #pragma warning restore RSEXPERIMENTAL001 - Contract.ThrowIfNull(result); - var original = Interlocked.CompareExchange(ref disableNullableAnalysis ? ref _nullableDisabledModel : ref _model, new WeakReference(result), null); + Contract.ThrowIfNull(result); + var original = Interlocked.CompareExchange(ref disableNullableAnalysis ? ref _nullableDisabledModel : ref _model, new WeakReference(result), null); - // okay, it is first time. - if (original == null) - { - return result; - } + // okay, it is first time. + if (original == null) + return result; - // It looks like someone has set it. Try to reuse same semantic model, or assign the new model if that - // fails. The lock is required since there is no compare-and-set primitive for WeakReference. - lock (original) - { - if (original.TryGetTarget(out semanticModel)) + // It looks like someone has set it. Try to reuse same semantic model, or assign the new model if that + // fails. The lock is required since there is no compare-and-set primitive for WeakReference. + lock (original) { - return semanticModel; - } + if (original.TryGetTarget(out var semanticModel)) + return semanticModel; - original.SetTarget(result); - return result; + original.SetTarget(result); + return result; + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - throw ExceptionUtilities.Unreachable(); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs new file mode 100644 index 0000000000000..95f2d1e1372a8 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis; + +internal partial class Workspace +{ + /// + /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure + /// that it won't be GC'ed between feature requests from multiple features that care about it. As the active + /// document has the most features running on it continuously, we definitely do not want to drop this. Note: this + /// cached value is only to help with performance. Not with correctness. Importantly, the concept of 'active + /// document' is itself fundamentally racy. That's ok though as we simply want to settle on these semantic models + /// settling into a stable state over time. We don't need to be perfect about it. They are intentionally not + /// locked either as we would only have contention right when switching to a new active document, and we would still + /// latch onto the new document very quickly. + /// + private SemanticModel? _activeDocumentSemanticModel; + + /// + private SemanticModel? _activeDocumentNullableDisabledSemanticModel; + + internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) + { + var service = this.Services.GetRequiredService(); + if (!service.SupportsDocumentTracking) + return; + + var activeDocumentId = service.TryGetActiveDocument(); + if (activeDocumentId != documentId) + return; + + // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that + // other features that wake up for this active document are sure to be able to reuse the same one. +#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API + if (semanticModel.NullableAnalysisIsDisabled) + _activeDocumentNullableDisabledSemanticModel = semanticModel; + else + _activeDocumentSemanticModel = semanticModel; +#pragma warning restore RSEXPERIMENTAL001 + } +} From a84d7a15b6f67537a68c3647f23eb4c8253956f2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:47:25 -0700 Subject: [PATCH 0604/1047] Add remote api --- .../Core/Remote/SolutionChecksumUpdater.cs | 38 +++++++++++++++++++ .../IDocumentTrackingServiceExtensions.cs | 3 -- .../IRemoteAssetSynchronizationService.cs | 20 +++++++++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 92187c42e3748..d0c707add134e 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -28,6 +28,8 @@ internal sealed class SolutionChecksumUpdater /// private readonly IGlobalOperationNotificationService? _globalOperationService; + private readonly IDocumentTrackingService _documentTrackingService; + /// /// Queue to push out text changes in a batched fashion when we hear about them. Because these should be short /// operations (only syncing text changes) we don't cancel this when we enter the paused state. We simply don't @@ -40,6 +42,11 @@ internal sealed class SolutionChecksumUpdater /// private readonly AsyncBatchingWorkQueue _synchronizeWorkspaceQueue; + /// + /// Queue for kicking off the work to synchronize the active document to the remote process. + /// + private readonly AsyncBatchingWorkQueue _synchronizeActiveDocumentQueue; + private readonly object _gate = new(); private bool _isPaused; @@ -53,6 +60,7 @@ public SolutionChecksumUpdater( _globalOperationService = workspace.Services.SolutionServices.ExportProvider.GetExports().FirstOrDefault()?.Value; _workspace = workspace; + _documentTrackingService = workspace.Services.GetRequiredService(); _textChangeQueue = new AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)>( DelayTimeSpan.NearImmediate, @@ -66,9 +74,17 @@ public SolutionChecksumUpdater( listener, shutdownToken); + _synchronizeActiveDocumentQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.NearImmediate, + SynchronizeActiveDocumentAsync, + listener, + shutdownToken); + // start listening workspace change event _workspace.WorkspaceChanged += OnWorkspaceChanged; + _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; + if (_globalOperationService != null) { _globalOperationService.Started += OnGlobalOperationStarted; @@ -84,6 +100,9 @@ public void Shutdown() // Try to stop any work that is in progress. PauseWork(); + var trackingService = _workspace.Services.GetRequiredService(); + trackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; + _workspace.WorkspaceChanged -= OnWorkspaceChanged; if (_globalOperationService != null) @@ -106,6 +125,7 @@ private void PauseWork() lock (_gate) { _synchronizeWorkspaceQueue.CancelExistingWork(); + _synchronizeActiveDocumentQueue.CancelExistingWork(); _isPaused = true; } } @@ -115,6 +135,7 @@ private void ResumeWork() lock (_gate) { _isPaused = false; + _synchronizeActiveDocumentQueue.AddWork(); _synchronizeWorkspaceQueue.AddWork(); } } @@ -137,6 +158,9 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) _synchronizeWorkspaceQueue.AddWork(); } + private void OnActiveDocumentChanged(object? sender, DocumentId? e) + => _synchronizeActiveDocumentQueue.AddWork(); + private async ValueTask SynchronizePrimaryWorkspaceAsync(CancellationToken cancellationToken) { var solution = _workspace.CurrentSolution; @@ -153,6 +177,20 @@ await client.TryInvokeAsync( } } + private async ValueTask SynchronizeActiveDocumentAsync(CancellationToken cancellationToken) + { + var activeDocument = _documentTrackingService.TryGetActiveDocument(); + + var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + if (client == null) + return; + + var solution = _workspace.CurrentSolution; + await client.TryInvokeAsync( + (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument), + cancellationToken).ConfigureAwait(false); + } + private async ValueTask SynchronizeTextChangesAsync( ImmutableSegmentedList<(Document? oldDocument, Document? newDocument)> values, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs index 6d3b0cff1a372..9a231ee40abbc 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs index 455c83c4177ed..5982dfe6dd27d 100644 --- a/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; @@ -16,5 +17,22 @@ internal interface IRemoteAssetSynchronizationService /// call into it. /// ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken); - ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, IEnumerable textChanges, CancellationToken cancellationToken); + + /// + /// Synchronize the text changes made by a user to a particular document as they are editing it. By sending over + /// the text changes as they happen, we can attempt to 'prime' the remote asset cache with a final that is built based off of retrieving the remote source text with a checksum corresponding + /// to and then applying the to it. Then, when + /// the next remote call comes in for the new solution snapshot, it can hopefully just pluck that text out of the + /// cache without having to sync the entire contents of the file over. + /// + ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken); + + /// + /// Synchronize over what the user's current active document is that they're editing. This can then be used by the + /// remote side to help determine which documents are best to strongly hold onto data for, and which should just + /// hold on weakly. Given how much work happens on the active document, this can help avoid the remote side from + /// continually creating and then throwing away that data. + /// + ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken); } From 9921a0181e42c7593e2702e79e85fd610bbe3fe3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:49:59 -0700 Subject: [PATCH 0605/1047] Move where cache is --- .../Core/Portable/Workspace/Solution/Document.cs | 2 +- .../Solution_SemanticModelCaching.cs} | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) rename src/Workspaces/Core/Portable/Workspace/{Workspace_SemanticModelCaching.cs => Solution/Solution_SemanticModelCaching.cs} (85%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 8f96644bb5ca3..379fabf46b317 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -330,7 +330,7 @@ internal async ValueTask GetRequiredNullableDisabledSemanticModel return null; var semanticModel = await GetSemanticModelWorkerAsync().ConfigureAwait(false); - this.Project.Solution.Workspace.OnSemanticModelObtained(this.Id, semanticModel); + this.Project.Solution.OnSemanticModelObtained(this.Id, semanticModel); return semanticModel; async Task GetSemanticModelWorkerAsync() diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs similarity index 85% rename from src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs index 95f2d1e1372a8..3d69d50db5287 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis; -internal partial class Workspace +internal partial class Solution { /// /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure @@ -23,10 +23,16 @@ internal partial class Workspace /// locked either as we would only have contention right when switching to a new active document, and we would still /// latch onto the new document very quickly. /// + /// + /// It is fine for these fields to never be read. The purpose is simply to keep a strong reference around so that + /// they will not be GC'ed as long as the active document stays the same. + /// +#pragma warning disable IDE0052 // Remove unread private members private SemanticModel? _activeDocumentSemanticModel; /// private SemanticModel? _activeDocumentNullableDisabledSemanticModel; +#pragma warning restore IDE0052 // Remove unread private members internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) { From 0bc3830afcf5fea6f1dd7607ecfe858bbcb69efa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:56:37 -0700 Subject: [PATCH 0606/1047] Simplify and docs --- .../DefaultDocumentTrackingService.cs | 1 - .../IDocumentTrackingService.cs | 27 ++++++--- .../ServiceHubDocumentTrackingService.cs | 60 ++++++++----------- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index c070a4f5ea9a1..3832ac9faed5b 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -17,7 +17,6 @@ internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingServic public bool SupportsDocumentTracking => false; public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public ImmutableArray GetVisibleDocuments() => []; diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs index 2e80613840883..6944f40fade72 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs @@ -8,25 +8,34 @@ namespace Microsoft.CodeAnalysis; +/// +/// Retrieves information about what documents are currently active or visible in the host workspace. Note: this +/// information is fundamentally racy (it can change directly after it is requested), and on different threads than the +/// thread that asks for it. As such, this information must only be used to provide a hint towards how a +/// feature should go about its work, it must not impact the final results that a feature produces. For example, a +/// feature is allowed to use this information to decide what order to process documents in, to try to get more relevant +/// results to a client more quickly. However, it is not allowed to use this information to decide what results to +/// return altogether. Hosts are free to implement this service to do nothing at all, always returning empty/default +/// values for the members within. As per the above, this should never affect correctness, but it may impede a +/// feature's ability to provide results in as timely a manner as possible for a client. +/// internal interface IDocumentTrackingService : IWorkspaceService { - bool SupportsDocumentTracking { get; } - /// - /// Get the of the active document. May be null if there is no active document - /// or the active document is not in the workspace. + /// Get the of the active document. May be null if there is no active document, the + /// active document is not in the workspace, or if this functionality is not supported by a particular host. /// DocumentId? TryGetActiveDocument(); /// - /// Get a read only collection of the s of all the visible documents in the workspace. + /// Get a read only collection of the s of all the visible documents in the workspace. May + /// be empty if there are no visible documents, or if this functionality is not supported by a particular host. /// ImmutableArray GetVisibleDocuments(); - event EventHandler ActiveDocumentChanged; - /// - /// Raised when a text buffer that's not part of a workspace is changed. + /// Fired when the active document changes. A host is not required to support this event, even if it implements + /// . /// - event EventHandler NonRoslynBufferTextChanged; + event EventHandler ActiveDocumentChanged; } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs index d3cbbf091565b..1a7d27770edc5 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -9,42 +9,32 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Host), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RemoteDocumentTrackingService() : IDocumentTrackingService { - [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Host)] - [Shared] - internal sealed class ServiceHubDocumentTrackingService : IDocumentTrackingService + public bool SupportsDocumentTracking => true; + + public event EventHandler ActiveDocumentChanged { add { } remove { } } + + public ImmutableArray GetVisibleDocuments() + => []; + + public DocumentId? TryGetActiveDocument() { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ServiceHubDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => false; - - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } - - public ImmutableArray GetVisibleDocuments() - { - Fail("Code should not be attempting to obtain visible documents from a stateless remote invocation."); - return []; - } - - public DocumentId? TryGetActiveDocument() - { - Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); - return null; - } - - private static void Fail(string message) - { - // assert in debug builds to hopefully catch problems in CI - Debug.Fail(message); - - // record NFW to see who violates contract. - FatalError.ReportAndCatch(new InvalidOperationException(message)); - } + Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); + return null; + } + + private static void Fail(string message) + { + // assert in debug builds to hopefully catch problems in CI + Debug.Fail(message); + + // record NFW to see who violates contract. + FatalError.ReportAndCatch(new InvalidOperationException(message)); } } From 7569a2a8bfefadf23c872a22611521f0c9d60b13 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:30:20 -0700 Subject: [PATCH 0607/1047] Report error creating delegate from partial generic method without implementation (#73060) --- .../Portable/Binder/Binder_Conversions.cs | 2 +- .../Semantic/Semantics/SemanticErrorTests.cs | 48 ++++++++++++++++++- .../Symbols/MethodSymbolExtensions.vb | 2 +- .../Semantic/Binding/BindingErrorTests.vb | 46 ++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index de77f9ff5d4cd..50ac6b57f6cdb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -2788,7 +2788,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/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 50c1950d2a1a4..5fb336b121a87 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() { 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/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( From 9c5cc52365e72aca6a6d327d9564463510694f29 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 13:40:16 -0700 Subject: [PATCH 0608/1047] Add support for remoting the active file --- .../Services/SolutionServiceTests.cs | 1399 ++++++++--------- .../RemoteAssetSynchronizationService.cs | 172 +- .../ServiceHubDocumentTrackingService.cs | 20 +- 3 files changed, 794 insertions(+), 797 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 287ddfe63a30b..2e7750b7f31ed 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -27,898 +27,897 @@ using Roslyn.Utilities; using Xunit; -namespace Roslyn.VisualStudio.Next.UnitTests.Remote +namespace Roslyn.VisualStudio.Next.UnitTests.Remote; + +[UseExportProvider] +[Trait(Traits.Feature, Traits.Features.RemoteHost)] +public class SolutionServiceTests { - [UseExportProvider] - [Trait(Traits.Feature, Traits.Features.RemoteHost)] - public class SolutionServiceTests + private static RemoteWorkspace CreateRemoteWorkspace() + => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + + [Fact] + public async Task TestCreation() { - private static RemoteWorkspace CreateRemoteWorkspace() - => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestCreation() - { - var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solution = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - } + [Theory] + [CombinatorialData] + public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) + { + var code1 = @"class Test1 { void Method() { } }"; - [Theory] - [CombinatorialData] - public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) - { - var code1 = @"class Test1 { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code1); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code1); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solution = workspace.CurrentSolution; - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, cancellationToken: CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, cancellationToken: CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); + } - Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); - } + [Fact] + public async Task TestStrongNameProvider() + { + using var workspace = new AdhocWorkspace(); + using var remoteWorkspace = CreateRemoteWorkspace(); - [Fact] - public async Task TestStrongNameProvider() - { - using var workspace = new AdhocWorkspace(); - using var remoteWorkspace = CreateRemoteWorkspace(); + var filePath = typeof(SolutionServiceTests).Assembly.Location; - var filePath = typeof(SolutionServiceTests).Assembly.Location; + workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, + filePath: filePath, outputFilePath: filePath)); - workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, - filePath: filePath, outputFilePath: filePath)); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var compilationOptions = solution.Projects.First().CompilationOptions; - var compilationOptions = solution.Projects.First().CompilationOptions; + Assert.IsType(compilationOptions.StrongNameProvider); + Assert.IsType(compilationOptions.XmlReferenceResolver); - Assert.IsType(compilationOptions.StrongNameProvider); - Assert.IsType(compilationOptions.XmlReferenceResolver); + var dirName = PathUtilities.GetDirectoryName(filePath); + var array = new[] { dirName, dirName }; + Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); + Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName); + } - var dirName = PathUtilities.GetDirectoryName(filePath); - var array = new[] { dirName, dirName }; - Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); - Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName); - } + [Fact] + public async Task TestStrongNameProviderEmpty() + { + using var workspace = new AdhocWorkspace(); + using var remoteWorkspace = CreateRemoteWorkspace(); - [Fact] - public async Task TestStrongNameProviderEmpty() - { - using var workspace = new AdhocWorkspace(); - using var remoteWorkspace = CreateRemoteWorkspace(); + var filePath = "testLocation"; - var filePath = "testLocation"; + workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, + filePath: filePath, outputFilePath: filePath)); - workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, - filePath: filePath, outputFilePath: filePath)); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var compilationOptions = solution.Projects.First().CompilationOptions; - var compilationOptions = solution.Projects.First().CompilationOptions; + Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider); + Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver); - Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider); - Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver); + var array = new string[] { }; + Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); + Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory); + } - var array = new string[] { }; - Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); - Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory); - } + [Fact] + public async Task TestCache() + { + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestCache() - { - var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + + // same instance from cache + Assert.True(object.ReferenceEquals(first, second)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind); + } - var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + [Fact] + public async Task TestUpdatePrimaryWorkspace() + { + var code = @"class Test { void Method() { } }"; - // same instance from cache - Assert.True(object.ReferenceEquals(first, second)); - Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind); - } + await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " "))); + } - [Fact] - public async Task TestUpdatePrimaryWorkspace() - { - var code = @"class Test { void Method() { } }"; + [Fact] + public async Task ProjectProperties() + { + using var workspace = TestWorkspace.CreateCSharp(""); - await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " "))); + static Solution SetProjectProperties(Solution solution, int version) + { + var projectId = solution.ProjectIds.Single(); + return solution + .WithProjectName(projectId, "Name" + version) + .WithProjectAssemblyName(projectId, "AssemblyName" + version) + .WithProjectFilePath(projectId, "FilePath" + version) + .WithProjectOutputFilePath(projectId, "OutputFilePath" + version) + .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) + .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) + .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) + .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) + .WithHasAllInformation(projectId, (version % 2) != 0) + .WithRunAnalyzers(projectId, (version % 2) != 0); } - [Fact] - public async Task ProjectProperties() + static void ValidateProperties(Solution solution, int version) { - using var workspace = TestWorkspace.CreateCSharp(""); - - static Solution SetProjectProperties(Solution solution, int version) - { - var projectId = solution.ProjectIds.Single(); - return solution - .WithProjectName(projectId, "Name" + version) - .WithProjectAssemblyName(projectId, "AssemblyName" + version) - .WithProjectFilePath(projectId, "FilePath" + version) - .WithProjectOutputFilePath(projectId, "OutputFilePath" + version) - .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) - .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) - .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) - .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) - .WithHasAllInformation(projectId, (version % 2) != 0) - .WithRunAnalyzers(projectId, (version % 2) != 0); - } - - static void ValidateProperties(Solution solution, int version) - { - var project = solution.Projects.Single(); - Assert.Equal("Name" + version, project.Name); - Assert.Equal("AssemblyName" + version, project.AssemblyName); - Assert.Equal("FilePath" + version, project.FilePath); - Assert.Equal("OutputFilePath" + version, project.OutputFilePath); - Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); - Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); - Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); - Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); - Assert.Equal((version % 2) != 0, project.State.HasAllInformation); - Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); - } - - Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); - - await VerifySolutionUpdate(workspace, - newSolutionGetter: s => SetProjectProperties(s, version: 1), - oldSolutionValidator: s => ValidateProperties(s, version: 0), - newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false); + var project = solution.Projects.Single(); + Assert.Equal("Name" + version, project.Name); + Assert.Equal("AssemblyName" + version, project.AssemblyName); + Assert.Equal("FilePath" + version, project.FilePath); + Assert.Equal("OutputFilePath" + version, project.OutputFilePath); + Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); + Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); + Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); + Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); + Assert.Equal((version % 2) != 0, project.State.HasAllInformation); + Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); } - [Fact] - public async Task TestUpdateDocumentInfo() - { - var code = @"class Test { void Method() { } }"; + Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); - await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); - } + await VerifySolutionUpdate(workspace, + newSolutionGetter: s => SetProjectProperties(s, version: 1), + oldSolutionValidator: s => ValidateProperties(s, version: 0), + newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false); + } + + [Fact] + public async Task TestUpdateDocumentInfo() + { + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestAddUpdateRemoveProjects() + await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); + } + + [Fact] + public async Task TestAddUpdateRemoveProjects() + { + var code = @"class Test { void Method() { } }"; + + await VerifySolutionUpdate(code, s => { - var code = @"class Test { void Method() { } }"; + var existingProjectId = s.ProjectIds.First(); - await VerifySolutionUpdate(code, s => - { - var existingProjectId = s.ProjectIds.First(); + s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution; - s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution; + var project = s.GetProject(existingProjectId); + project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified")); - var project = s.GetProject(existingProjectId); - project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified")); + var existingDocumentId = project.DocumentIds.First(); - var existingDocumentId = project.DocumentIds.First(); + project = project.AddDocument("newDocument", SourceText.From("// new text")).Project; - project = project.AddDocument("newDocument", SourceText.From("// new text")).Project; + var document = project.GetDocument(existingDocumentId); - var document = project.GetDocument(existingDocumentId); + document = document.WithSourceCodeKind(SourceCodeKind.Script); - document = document.WithSourceCodeKind(SourceCodeKind.Script); + return document.Project.Solution; + }); + } - return document.Project.Solution; - }); - } + [Fact] + public async Task TestAdditionalDocument() + { + var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var additionalDocumentId = DocumentId.CreateNewId(projectId); + var additionalDocumentInfo = DocumentInfo.Create( + additionalDocumentId, "additionalFile", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create()))); - [Fact] - public async Task TestAdditionalDocument() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var additionalDocumentId = DocumentId.CreateNewId(projectId); - var additionalDocumentInfo = DocumentInfo.Create( - additionalDocumentId, "additionalFile", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create()))); - - await VerifySolutionUpdate(workspace, s => - { - return s.AddAdditionalDocument(additionalDocumentInfo); - }); - - workspace.OnAdditionalDocumentAdded(additionalDocumentInfo); - - await VerifySolutionUpdate(workspace, s => - { - return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed")); - }); - - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveAdditionalDocument(additionalDocumentId); - }); - } + return s.AddAdditionalDocument(additionalDocumentInfo); + }); - [Fact] - public async Task TestAnalyzerConfigDocument() + workspace.OnAdditionalDocumentAdded(additionalDocumentInfo); + + await VerifySolutionUpdate(workspace, s => { - var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig"); - var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); - var analyzerConfigDocumentInfo = DocumentInfo.Create( - analyzerConfigDocumentId, - name: ".editorconfig", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)), - filePath: configPath); - - await VerifySolutionUpdate(workspace, s => - { - return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo)); - }); - - workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo); - - await VerifySolutionUpdate(workspace, s => - { - return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false")); - }); - - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId); - }); - } + return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed")); + }); - [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] - public async Task TestDocument() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; + return s.RemoveAdditionalDocument(additionalDocumentId); + }); + } + + [Fact] + public async Task TestAnalyzerConfigDocument() + { + var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig"); + var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); + var analyzerConfigDocumentInfo = DocumentInfo.Create( + analyzerConfigDocumentId, + name: ".editorconfig", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)), + filePath: configPath); + + await VerifySolutionUpdate(workspace, s => + { + return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo)); + }); - using var workspace = TestWorkspace.CreateCSharp(code); + workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo); - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var documentId = DocumentId.CreateNewId(projectId); - var documentInfo = DocumentInfo.Create( - documentId, "sourceFile", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create()))); + await VerifySolutionUpdate(workspace, s => + { + return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false")); + }); - await VerifySolutionUpdate(workspace, s => - { - return s.AddDocument(documentInfo); - }); + await VerifySolutionUpdate(workspace, s => + { + return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId); + }); + } - workspace.OnDocumentAdded(documentInfo); + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task TestDocument() + { + var code = @"class Test { void Method() { } }"; - await VerifySolutionUpdate(workspace, s => - { - return s.WithDocumentText(documentId, SourceText.From("class Changed { }")); - }); + using var workspace = TestWorkspace.CreateCSharp(code); - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveDocument(documentId); - }); - } + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var documentId = DocumentId.CreateNewId(projectId); + var documentInfo = DocumentInfo.Create( + documentId, "sourceFile", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create()))); - [Fact] - public async Task TestRemoteWorkspace() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; + return s.AddDocument(documentInfo); + }); - // create base solution - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + workspace.OnDocumentAdded(documentInfo); - // create solution service - var solution1 = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1); + await VerifySolutionUpdate(workspace, s => + { + return s.WithDocumentText(documentId, SourceText.From("class Changed { }")); + }); - var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1); + await VerifySolutionUpdate(workspace, s => + { + return s.RemoveDocument(documentId); + }); + } - await Verify(remoteWorkspace, solution1, remoteSolution1); + [Fact] + public async Task TestRemoteWorkspace() + { + var code = @"class Test { void Method() { } }"; - // update remote workspace - var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }")); - var oopSolution2 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); + // create base solution + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - await Verify(remoteWorkspace, currentSolution, oopSolution2); + // create solution service + var solution1 = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1); - // move backward - await Verify(remoteWorkspace, remoteSolution1, await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(remoteSolution1)); + var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1); - // move forward - currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }")); - var remoteSolution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); + await Verify(remoteWorkspace, solution1, remoteSolution1); - await Verify(remoteWorkspace, currentSolution, remoteSolution3); + // update remote workspace + var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }")); + var oopSolution2 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); - // move to new solution backward - var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); - var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); + await Verify(remoteWorkspace, currentSolution, oopSolution2); - // move to new solution forward - var solution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(solution2); - Assert.NotNull(solution3); - await Verify(remoteWorkspace, solution1, solution3); + // move backward + await Verify(remoteWorkspace, remoteSolution1, await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(remoteSolution1)); - static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) - { - // set up initial solution - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + // move forward + currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }")); + var remoteSolution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); - // get solution in remote host - return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - } + await Verify(remoteWorkspace, currentSolution, remoteSolution3); - static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution) - { - // verify we got solution expected - Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + // move to new solution backward + var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); + var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); - // verify remote workspace got updated - Assert.Equal(remoteSolution, remoteWorkspace.CurrentSolution); - } - } + // move to new solution forward + var solution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(solution2); + Assert.NotNull(solution3); + await Verify(remoteWorkspace, solution1, solution3); - [Theory, CombinatorialData] - [WorkItem("https://github.com/dotnet/roslyn/issues/48564")] - public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue) + static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) { - using var workspace = TestWorkspace.CreateCSharp(@"public class C { }"); - using var remoteWorkspace = CreateRemoteWorkspace(); - - // Initial empty solution - var solution = workspace.CurrentSolution; - solution = solution.RemoveProject(solution.ProjectIds.Single()); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + // set up initial solution var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Add a C# project and a VB project, set some options, and check again - var csharpDocument = new TestHostDocument("public class C { }"); - var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2"); - var csharpProjectInfo = csharpProject.ToProjectInfo(); - - var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class"); - var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3"); - var vbProjectInfo = vbProject.ToProjectInfo(); - - solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo); - var newOptionValue = useDefaultOptionValue - ? FormattingOptions2.NewLine.DefaultValue - : FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue; - solution = solution.WithOptions(solution.Options - .WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue) - .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + + // get solution in remote host + return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); } - [Fact] - public async Task TestFrozenSourceGeneratedDocument() + static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution) { - using var workspace = TestWorkspace.CreateCSharp(@""); - using var remoteWorkspace = CreateRemoteWorkspace(); - - var solution = workspace.CurrentSolution - .Projects.Single() - .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) - .Solution; + // verify we got solution expected + Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // First sync the solution over that has a generator - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Now freeze with some content - var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; - var frozenText1 = SourceText.From("// Hello, World!"); - var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText1).Project.Solution; - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); - solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Try freezing with some different content from the original solution - var frozenText2 = SourceText.From("// Hello, World! A second time!"); - var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText2).Project.Solution; - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); - solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + // verify remote workspace got updated + Assert.Equal(remoteSolution, remoteWorkspace.CurrentSolution); } + } - [Fact] - public async Task TestPartialProjectSync_GetSolutionFirst() - { - var code = @"class Test { void Method() { } }"; + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/48564")] + public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue) + { + using var workspace = TestWorkspace.CreateCSharp(@"public class C { }"); + using var remoteWorkspace = CreateRemoteWorkspace(); + + // Initial empty solution + var solution = workspace.CurrentSolution; + solution = solution.RemoveProject(solution.ProjectIds.Single()); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Add a C# project and a VB project, set some options, and check again + var csharpDocument = new TestHostDocument("public class C { }"); + var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2"); + var csharpProjectInfo = csharpProject.ToProjectInfo(); + + var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class"); + var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3"); + var vbProjectInfo = vbProject.ToProjectInfo(); + + solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo); + var newOptionValue = useDefaultOptionValue + ? FormattingOptions2.NewLine.DefaultValue + : FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue; + solution = solution.WithOptions(solution.Options + .WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue) + .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + [Fact] + public async Task TestFrozenSourceGeneratedDocument() + { + using var workspace = TestWorkspace.CreateCSharp(@""); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution + .Projects.Single() + .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) + .Solution; + + // First sync the solution over that has a generator + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Now freeze with some content + var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; + var frozenText1 = SourceText.From("// Hello, World!"); + var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText1).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); + solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Try freezing with some different content from the original solution + var frozenText2 = SourceText.From("// Hello, World! A second time!"); + var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText2).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); + solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetSolutionFirst() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); + solution = project2.Solution; - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - Assert.Equal(2, syncedFullSolution.Projects.Count()); + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); - // Syncing project1 should do nothing as syncing the solution already synced it over. - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - // Syncing project2 should do nothing as syncing the solution already synced it over. - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); - } + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(2, syncedFullSolution.Projects.Count()); - [Fact] - public async Task TestPartialProjectSync_GetSolutionLast() - { - var code = @"class Test { void Method() { } }"; + // Syncing project1 should do nothing as syncing the solution already synced it over. + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Syncing project2 should do nothing as syncing the solution already synced it over. + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetSolutionLast() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Syncing project 1 should just since it over. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + solution = project2.Solution; - // Syncing project 2 should end up with only p2 synced over. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - // then syncing the whole project should now copy both over. - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + // Syncing project 1 should just since it over. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); - Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - Assert.Equal(2, syncedFullSolution.Projects.Count()); - } + // Syncing project 2 should end up with only p2 synced over. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects1() - { - var code = @"class Test { void Method() { } }"; + // then syncing the whole project should now copy both over. + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(2, syncedFullSolution.Projects.Count()); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects1() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); - Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); - - // syncing project 3 should sync project 2 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project3SyncedSolution.Projects.Count()); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects2() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); - var solution = workspace.CurrentSolution; + // syncing project 3 should sync project 2 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project3SyncedSolution.Projects.Count()); + } - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects2() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + + solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // syncing P3 should since project P2 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project3SyncedSolution.Projects.Count()); + + // if we then sync just P2, we should still have only P2 in the synced cone + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + AssertEx.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); + + // if we then sync just P1, we should only have it in its own cone. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + AssertEx.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects3() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing P3 should since project P2 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // if we then sync just P2, we should still have only P2 in the synced cone - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); - AssertEx.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - // if we then sync just P1, we should only have it in its own cone. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - AssertEx.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) + .AddProjectReference(project2.Id, new(project1.Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects3() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // syncing project3 should since project2 and project1 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(3, project3SyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // syncing project2 should only have it and project 1. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + // syncing project1 should only be itself + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) - .AddProjectReference(project2.Id, new(project1.Id)); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects4() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing project3 should since project2 and project1 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(3, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // syncing project2 should only have it and project 1. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - // syncing project1 should only be itself - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) + .AddProjectReference(project3.Id, new(project1.Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects4() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // syncing project3 should since project2 and project1 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(3, project3SyncedSolution.Projects.Count()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Syncing project2 should only have a cone with itself. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // Syncing project1 should only have a cone with itself. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + } - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + [Fact] + public async Task TestPartialProjectSync_Options1() + { + var code = @"class Test { void Method() { } }"; - solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) - .AddProjectReference(project3.Id, new(project1.Id)); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var solution = workspace.CurrentSolution; - // syncing project3 should since project2 and project1 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(3, project3SyncedSolution.Projects.Count()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); - // Syncing project2 should only have a cone with itself. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + solution = project2.Solution; - // Syncing project1 should only have a cone with itself. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - } + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - [Fact] - public async Task TestPartialProjectSync_Options1() - { - var code = @"class Test { void Method() { } }"; + // Syncing over project1 should give us 1 set of options on the OOP side. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + + // Syncing over project2 should also only be one set of options. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + } + + [Fact] + public async Task TestPartialProjectSync_DoesNotSeeChangesOutsideOfCone() + { + var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var solution = workspace.CurrentSolution; + var solution = workspace.CurrentSolution; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); - solution = project2.Solution; + solution = project2.Solution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - // Syncing over project1 should give us 1 set of options on the OOP side. + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(2, fullSyncedSolution.Projects.Count()); + + // Mutate both projects to each have a document in it. + solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; + solution = solution.GetProject(project2.Id).AddDocument("Y.vb", SourceText.From("' Y")).Project.Solution; + + // Now just sync project1's cone over. We should not see the change to project2 on the remote side. + // But we will still see project2. + { await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var csharpProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); + var vbProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + Assert.True(csharpProject.DocumentIds.Count == 2); + Assert.Empty(vbProject.DocumentIds); + } - // Syncing over project2 should also only be one set of options. + // Similarly, if we sync just project2's cone over: + { await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); + var csharpProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); + var vbProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + Assert.Single(csharpProject.DocumentIds); + Assert.Single(vbProject.DocumentIds); } + } - [Fact] - public async Task TestPartialProjectSync_DoesNotSeeChangesOutsideOfCone() - { - var code = @"class Test { void Method() { } }"; - - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); - - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_AddP2PRef() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(2, fullSyncedSolution.Projects.Count()); - - // Mutate both projects to each have a document in it. - solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; - solution = solution.GetProject(project2.Id).AddDocument("Y.vb", SourceText.From("' Y")).Project.Solution; - - // Now just sync project1's cone over. We should not see the change to project2 on the remote side. - // But we will still see project2. - { - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); - var csharpProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); - var vbProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); - Assert.True(csharpProject.DocumentIds.Count == 2); - Assert.Empty(vbProject.DocumentIds); - } - - // Similarly, if we sync just project2's cone over: - { - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); - var csharpProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); - var vbProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); - Assert.Single(csharpProject.DocumentIds); - Assert.Single(vbProject.DocumentIds); - } - } + solution = project2.Solution; - [Fact] - public async Task TestPartialProjectSync_AddP2PRef() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(2, fullSyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // Mutate both projects to have a document in it, and add a p2p ref from project1 to project2 + solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; + solution = solution.GetProject(project2.Id).AddDocument("Y.cs", SourceText.From("// Y")).Project.Solution; + solution = solution.GetProject(project1.Id).AddProjectReference(new ProjectReference(project2.Id)).Solution; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - - solution = project2.Solution; - - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + // Now just sync project1's cone over. This will validate that the p2p ref doesn't try to add a new + // project, but instead sees the existing one. + { + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var project1Synced = project1SyncedSolution.GetRequiredProject(project1.Id); + var project2Synced = project1SyncedSolution.GetRequiredProject(project2.Id); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(2, fullSyncedSolution.Projects.Count()); - - // Mutate both projects to have a document in it, and add a p2p ref from project1 to project2 - solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; - solution = solution.GetProject(project2.Id).AddDocument("Y.cs", SourceText.From("// Y")).Project.Solution; - solution = solution.GetProject(project1.Id).AddProjectReference(new ProjectReference(project2.Id)).Solution; - - // Now just sync project1's cone over. This will validate that the p2p ref doesn't try to add a new - // project, but instead sees the existing one. - { - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); - var project1Synced = project1SyncedSolution.GetRequiredProject(project1.Id); - var project2Synced = project1SyncedSolution.GetRequiredProject(project2.Id); - - Assert.True(project1Synced.DocumentIds.Count == 2); - Assert.Single(project2Synced.DocumentIds); - Assert.Single(project1Synced.ProjectReferences); - } + Assert.True(project1Synced.DocumentIds.Count == 2); + Assert.Single(project2Synced.DocumentIds); + Assert.Single(project1Synced.ProjectReferences); } + } - [Fact] - public async Task TestPartialProjectSync_ReferenceToNonExistentProject() - { - var code = @"class Test { void Method() { } }"; + [Fact] + public async Task TestPartialProjectSync_ReferenceToNonExistentProject() + { + var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var solution = workspace.CurrentSolution; + var solution = workspace.CurrentSolution; - var project1 = solution.Projects.Single(); + var project1 = solution.Projects.Single(); - // This reference a project that doesn't exist. - // Ensure that it's still fine to get the checksum for this project we have. - project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); + // This reference a project that doesn't exist. + // Ensure that it's still fine to get the checksum for this project we have. + project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); - solution = project1.Solution; + solution = project1.Solution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - } + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + } - private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) - { - using var workspace = TestWorkspace.CreateCSharp(code); - await VerifySolutionUpdate(workspace, newSolutionGetter); - } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) + { + using var workspace = TestWorkspace.CreateCSharp(code); + await VerifySolutionUpdate(workspace, newSolutionGetter); + } - private static async Task VerifySolutionUpdate( - TestWorkspace workspace, - Func newSolutionGetter, - Action oldSolutionValidator = null, - Action newSolutionValidator = null) - { - var solution = workspace.CurrentSolution; - oldSolutionValidator?.Invoke(solution); + private static async Task VerifySolutionUpdate( + TestWorkspace workspace, + Func newSolutionGetter, + Action oldSolutionValidator = null, + Action newSolutionValidator = null) + { + var solution = workspace.CurrentSolution; + oldSolutionValidator?.Invoke(solution); - var map = new Dictionary(); + var map = new Dictionary(); - using var remoteWorkspace = CreateRemoteWorkspace(); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + using var remoteWorkspace = CreateRemoteWorkspace(); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - // update primary workspace - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); - var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - oldSolutionValidator?.Invoke(recoveredSolution); + // update primary workspace + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + oldSolutionValidator?.Invoke(recoveredSolution); - Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); - Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); + Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // get new solution - var newSolution = newSolutionGetter(solution); - var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - await newSolution.AppendAssetMapAsync(map, CancellationToken.None); + // get new solution + var newSolution = newSolutionGetter(solution); + var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + await newSolution.AppendAssetMapAsync(map, CancellationToken.None); - // get solution without updating primary workspace - var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + // get solution without updating primary workspace + var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // do same once updating primary workspace - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, CancellationToken.None); - var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + // do same once updating primary workspace + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, CancellationToken.None); + var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); - newSolutionValidator?.Invoke(recoveredNewSolution); - } + newSolutionValidator?.Invoke(recoveredNewSolution); + } - private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) - { - // make sure checksum is calculated - await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) + { + // make sure checksum is calculated + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - map ??= []; - await solution.AppendAssetMapAsync(map, CancellationToken.None); + map ??= []; + await solution.AppendAssetMapAsync(map, CancellationToken.None); - var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); - var storage = new SolutionAssetCache(); - var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); + var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); + var storage = new SolutionAssetCache(); + var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); - return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - } + return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index 5d27541525254..7f2e7b27674a3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -3,112 +3,118 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in the +/// out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after +/// an edit once a feature is asking for a snapshot. +/// +internal sealed class RemoteAssetSynchronizationService(in BrokeredServiceBase.ServiceConstructionArguments arguments) + : BrokeredServiceBase(in arguments), IRemoteAssetSynchronizationService { - /// - /// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in the - /// out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after - /// an edit once a feature is asking for a snapshot. - /// - internal sealed class RemoteAssetSynchronizationService : BrokeredServiceBase, IRemoteAssetSynchronizationService + internal sealed class Factory : FactoryBase { - internal sealed class Factory : FactoryBase - { - protected override IRemoteAssetSynchronizationService CreateService(in ServiceConstructionArguments arguments) - => new RemoteAssetSynchronizationService(in arguments); - } + protected override IRemoteAssetSynchronizationService CreateService(in ServiceConstructionArguments arguments) + => new RemoteAssetSynchronizationService(in arguments); + } - public RemoteAssetSynchronizationService(in ServiceConstructionArguments arguments) - : base(in arguments) + public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => { - } + using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizePrimaryWorkspaceAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) + { + var workspace = GetWorkspace(); + var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); + await workspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); + } + + public ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken) + { + var documentTrackingService = GetWorkspace().Services.GetRequiredService() as RemoteDocumentTrackingService; + if (documentTrackingService is null) + return ValueTaskFactory.CompletedTask; - public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken) + documentTrackingService.SetActiveDocument(documentId); + } + + public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => { - return RunServiceAsync(async cancellationToken => + var workspace = GetWorkspace(); + + using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) { - using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizePrimaryWorkspaceAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) + var serializer = workspace.Services.GetRequiredService(); + + // Try to get the text associated with baseTextChecksum + var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); + if (text == null) { - var workspace = GetWorkspace(); - var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); - await workspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false); + // it won't bring in base text if it is not there already. + // text needed will be pulled in when there is request + return; } - }, cancellationToken); - } - public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, IEnumerable textChanges, CancellationToken cancellationToken) - { - return RunServiceAsync(async cancellationToken => - { - var workspace = GetWorkspace(); + // Now attempt to manually apply the edit, producing the new forked text. Store that directly in + // the asset cache so that future calls to retrieve it can do so quickly, without synchronizing over + // the entire document. + var newText = text.WithChanges(textChanges); + var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); + var newChecksum = serializer.CreateChecksum(newSerializableText, cancellationToken); + + WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newSerializableText); + } - using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) + return; + + async static Task TryGetSourceTextAsync( + RemoteWorkspaceManager workspaceManager, + Workspace workspace, + DocumentId documentId, + Checksum baseTextChecksum, + CancellationToken cancellationToken) + { + // check the cheap and fast one first. + // see if the cache has the source text + if (workspaceManager.SolutionAssetCache.TryGetAsset(baseTextChecksum, out var serializableSourceText)) { - var serializer = workspace.Services.GetRequiredService(); - - // Try to get the text associated with baseTextChecksum - var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); - if (text == null) - { - // it won't bring in base text if it is not there already. - // text needed will be pulled in when there is request - return; - } - - // Now attempt to manually apply the edit, producing the new forked text. Store that directly in - // the asset cache so that future calls to retrieve it can do so quickly, without synchronizing over - // the entire document. - var newText = text.WithChanges(textChanges); - var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); - var newChecksum = serializer.CreateChecksum(newSerializableText, cancellationToken); - - WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newSerializableText); + return await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); } - return; + // do slower one + // check whether existing solution has it + var document = workspace.CurrentSolution.GetDocument(documentId); + if (document == null) + { + return null; + } - async static Task TryGetSourceTextAsync( - RemoteWorkspaceManager workspaceManager, - Workspace workspace, - DocumentId documentId, - Checksum baseTextChecksum, - CancellationToken cancellationToken) + // check checksum whether it is there. + // since we lazily synchronize whole solution (SynchronizePrimaryWorkspaceAsync) when things are idle, + // soon or later this will get hit even if text changes got out of sync due to issues in VS side + // such as file is first opened and there is no SourceText in memory yet. + if (!document.State.TryGetStateChecksums(out var state) || + !state.Text.Equals(baseTextChecksum)) { - // check the cheap and fast one first. - // see if the cache has the source text - if (workspaceManager.SolutionAssetCache.TryGetAsset(baseTextChecksum, out var serializableSourceText)) - { - return await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - } - - // do slower one - // check whether existing solution has it - var document = workspace.CurrentSolution.GetDocument(documentId); - if (document == null) - { - return null; - } - - // check checksum whether it is there. - // since we lazily synchronize whole solution (SynchronizePrimaryWorkspaceAsync) when things are idle, - // soon or later this will get hit even if text changes got out of sync due to issues in VS side - // such as file is first opened and there is no SourceText in memory yet. - if (!document.State.TryGetStateChecksums(out var state) || - !state.Text.Equals(baseTextChecksum)) - { - return null; - } - - return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + return null; } - }, cancellationToken); - } + + return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs index 1a7d27770edc5..4a83c398ac9b6 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Remote; @@ -16,25 +14,19 @@ namespace Microsoft.CodeAnalysis.Remote; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class RemoteDocumentTrackingService() : IDocumentTrackingService { - public bool SupportsDocumentTracking => true; + private DocumentId? _activeDocument; - public event EventHandler ActiveDocumentChanged { add { } remove { } } + public event EventHandler? ActiveDocumentChanged; public ImmutableArray GetVisibleDocuments() => []; public DocumentId? TryGetActiveDocument() - { - Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); - return null; - } + => _activeDocument; - private static void Fail(string message) + internal void SetActiveDocument(DocumentId? documentId) { - // assert in debug builds to hopefully catch problems in CI - Debug.Fail(message); - - // record NFW to see who violates contract. - FatalError.ReportAndCatch(new InvalidOperationException(message)); + _activeDocument = documentId; + ActiveDocumentChanged?.Invoke(this, documentId); } } From f23e03eb70c3864a30204b9d7250ac6f77028447 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 13:55:40 -0700 Subject: [PATCH 0609/1047] tests and simplification --- .../NavigateTo/NavigateToSearcherTests.cs | 2 +- .../Core/Remote/SolutionChecksumUpdater.cs | 22 ++-- .../NavigateTo/AbstractNavigateToTests.cs | 2 +- ...ctiveAndVisibleDocumentTrackingService.cs} | 13 +-- .../Services/SolutionServiceTests.cs | 104 +++++++++++++++++- .../Solution/Solution_SemanticModelCaching.cs | 12 +- 6 files changed, 120 insertions(+), 35 deletions(-) rename src/EditorFeatures/TestUtilities/{NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs => Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs} (69%) diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 24c066414e32d..7092c2679be22 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.NavigateTo [Trait(Traits.Feature, Traits.Features.NavigateTo)] public sealed class NavigateToSearcherTests { - private static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); + private static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static void SetupSearchProject( Mock searchService, diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index d0c707add134e..53322e1d31de7 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -35,7 +35,7 @@ internal sealed class SolutionChecksumUpdater /// operations (only syncing text changes) we don't cancel this when we enter the paused state. We simply don't /// start queuing more requests into this until we become unpaused. /// - private readonly AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)> _textChangeQueue; + private readonly AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)> _textChangeQueue; /// /// Queue for kicking off the work to synchronize the primary workspace's solution. @@ -62,7 +62,7 @@ public SolutionChecksumUpdater( _workspace = workspace; _documentTrackingService = workspace.Services.GetRequiredService(); - _textChangeQueue = new AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)>( + _textChangeQueue = new AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)>( DelayTimeSpan.NearImmediate, SynchronizeTextChangesAsync, listener, @@ -152,7 +152,10 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) if (e.Kind == WorkspaceChangeKind.DocumentChanged) { - _textChangeQueue.AddWork((e.OldSolution.GetDocument(e.DocumentId), e.NewSolution.GetDocument(e.DocumentId))); + var oldDocument = e.OldSolution.GetDocument(e.DocumentId); + var newDocument = e.NewSolution.GetDocument(e.DocumentId); + if (oldDocument != null && newDocument != null) + _textChangeQueue.AddWork((oldDocument, newDocument)); } _synchronizeWorkspaceQueue.AddWork(); @@ -187,19 +190,16 @@ private async ValueTask SynchronizeActiveDocumentAsync(CancellationToken cancell var solution = _workspace.CurrentSolution; await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument), + (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument, cancellationToken), cancellationToken).ConfigureAwait(false); } private async ValueTask SynchronizeTextChangesAsync( - ImmutableSegmentedList<(Document? oldDocument, Document? newDocument)> values, + ImmutableSegmentedList<(Document oldDocument, Document newDocument)> values, CancellationToken cancellationToken) { foreach (var (oldDocument, newDocument) in values) { - if (oldDocument is null || newDocument is null) - continue; - cancellationToken.ThrowIfCancellationRequested(); await SynchronizeTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); } @@ -232,15 +232,15 @@ async ValueTask SynchronizeTextChangesAsync(Document oldDocument, Document newDo } // get text changes - var textChanges = newText.GetTextChanges(oldText); - if (textChanges.Count == 0) + var textChanges = newText.GetTextChanges(oldText).AsImmutable(); + if (textChanges.Length == 0) { // no changes return; } // whole document case - if (textChanges.Count == 1 && textChanges[0].Span.Length == oldText.Length) + if (textChanges.Length == 1 && textChanges[0].Span.Length == oldText.Length) { // no benefit here. pulling from remote host is more efficient return; diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index b9f70d5b16fc5..f494a0f9f0969 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -41,7 +41,7 @@ public abstract partial class AbstractNavigateToTests { protected static readonly TestComposition DefaultComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService)); protected static readonly TestComposition FirstVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsVisibleDocumentTrackingService.Factory)); - protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); + protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); protected INavigateToItemProvider _provider; protected NavigateToTestAggregator _aggregator; diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs similarity index 69% rename from src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs rename to src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs index 08628f9c6e68e..55fc88be78ef8 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs @@ -10,26 +10,23 @@ using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo; +namespace Microsoft.CodeAnalysis.Test.Utilities; -internal sealed class FirstDocIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService +internal sealed class FirstDocumentIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService { private readonly Workspace _workspace; [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - private FirstDocIsActiveAndVisibleDocumentTrackingService(Workspace workspace) + private FirstDocumentIsActiveAndVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public DocumentId TryGetActiveDocument() => _workspace.CurrentSolution.Projects.First().DocumentIds.First(); public ImmutableArray GetVisibleDocuments() - => ImmutableArray.Create(_workspace.CurrentSolution.Projects.First().DocumentIds.First()); + => [TryGetActiveDocument()]; [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] public class Factory : IWorkspaceServiceFactory @@ -42,6 +39,6 @@ public Factory() [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new FirstDocIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); + => new FirstDocumentIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); } } diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 2e7750b7f31ed..a8bf873d90ee2 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -13,14 +13,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -33,6 +30,9 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; [Trait(Traits.Feature, Traits.Features.RemoteHost)] public class SolutionServiceTests { + private static TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = + FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService)); + private static RemoteWorkspace CreateRemoteWorkspace() => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -858,6 +858,104 @@ public async Task TestPartialProjectSync_ReferenceToNonExistentProject() var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); } + [Fact] + public async Task TestNoActiveDocumentSemanticModelNotCached() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Without anything holding onto the semantic model, it should get releases. + var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + + objectReference.AssertReleased(); + } + + [Fact] + public async Task TestActiveDocumentSemanticModelCached() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Without anything holding onto the semantic model, it should get releases. + var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + + objectReference.AssertHeld(); + } + + [Fact] + public async Task TestOnlyActiveDocumentSemanticModelCached() + { + using var workspace = TestWorkspace.Create(""" + + + + class Program1 + { + } + + + class Program2 + { + } + + + """, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.First(); + var document2 = project1.Documents.Last(); + + // Only the semantic model for the active document should be cached. + var objectReference1 = new ObjectReference(await document1.GetSemanticModelAsync()); + var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); + + objectReference1.AssertHeld(); + objectReference1.AssertRelease(); + } + + [Fact] + public async Task TestActiveDocumentSemanticModelCached2() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + + // This reference a project that doesn't exist. + // Ensure that it's still fine to get the checksum for this project we have. + project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); + + solution = project1.Solution; + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs index 3d69d50db5287..970ed107c0b2f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -2,16 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.CodeAnalysis; -internal partial class Solution +public partial class Solution { /// /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure @@ -37,9 +30,6 @@ internal partial class Solution internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) { var service = this.Services.GetRequiredService(); - if (!service.SupportsDocumentTracking) - return; - var activeDocumentId = service.TryGetActiveDocument(); if (activeDocumentId != documentId) return; From adb0f2eb4a4b4d952b9251d1e6c25f86cc807826 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 13:57:40 -0700 Subject: [PATCH 0610/1047] Fixes --- .../Core/Test.Next/Services/ServiceHubServicesTests.cs | 2 +- .../Core/Test.Next/Services/SolutionServiceTests.cs | 6 +++--- .../RemoteAssetSynchronizationService.cs | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 1a9747fc226a7..893cc20f5e94f 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -91,7 +91,7 @@ public async Task TestRemoteHostTextSynchronize() // sync await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeTextAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText), cancellationToken), + (service, cancellationToken) => service.SynchronizeTextAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText).AsImmutable(), cancellationToken), CancellationToken.None); // apply change to solution diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index a8bf873d90ee2..fa20a6571a403 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -882,7 +882,7 @@ public async Task TestActiveDocumentSemanticModelCached() { var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); using var remoteWorkspace = CreateRemoteWorkspace(); var solution = workspace.CurrentSolution; @@ -913,7 +913,7 @@ class Program2 } - """, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + """, composition: s_compositionWithFirstDocumentIsActiveAndVisible); using var remoteWorkspace = CreateRemoteWorkspace(); var solution = workspace.CurrentSolution; @@ -927,7 +927,7 @@ class Program2 var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); objectReference1.AssertHeld(); - objectReference1.AssertRelease(); + objectReference1.AssertReleased(); } [Fact] diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index 7f2e7b27674a3..f46c290d40e5d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -44,10 +44,8 @@ public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, Can public ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken) { var documentTrackingService = GetWorkspace().Services.GetRequiredService() as RemoteDocumentTrackingService; - if (documentTrackingService is null) - return ValueTaskFactory.CompletedTask; - - documentTrackingService.SetActiveDocument(documentId); + documentTrackingService?.SetActiveDocument(documentId); + return ValueTaskFactory.CompletedTask; } public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken) From 49c30fa41d835d54b746f04d6fa14974c1421996 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:05:52 -0700 Subject: [PATCH 0611/1047] Fix test --- .../Core/Test.Next/Services/SolutionServiceTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index fa20a6571a403..213e979be078e 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -912,6 +912,7 @@ class Program2 { } + """, composition: s_compositionWithFirstDocumentIsActiveAndVisible); using var remoteWorkspace = CreateRemoteWorkspace(); @@ -927,7 +928,7 @@ class Program2 var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); objectReference1.AssertHeld(); - objectReference1.AssertReleased(); + objectReference2.AssertReleased(); } [Fact] From 27aab15f7c2ec26e3b0ccc1ac55572990c8eada0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:14:53 -0700 Subject: [PATCH 0612/1047] Fix test --- .../Services/SolutionServiceTests.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 213e979be078e..e8eb78cdbb173 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -30,8 +30,8 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; [Trait(Traits.Feature, Traits.Features.RemoteHost)] public class SolutionServiceTests { - private static TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = - FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService)); + private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = + FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -859,7 +859,7 @@ public async Task TestPartialProjectSync_ReferenceToNonExistentProject() } [Fact] - public async Task TestNoActiveDocumentSemanticModelNotCached() + public void TestNoActiveDocumentSemanticModelNotCached() { var code = @"class Test { void Method() { } }"; @@ -872,13 +872,13 @@ public async Task TestNoActiveDocumentSemanticModelNotCached() var document1 = project1.Documents.Single(); // Without anything holding onto the semantic model, it should get releases. - var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + var objectReference = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); objectReference.AssertReleased(); } [Fact] - public async Task TestActiveDocumentSemanticModelCached() + public void TestActiveDocumentSemanticModelCached() { var code = @"class Test { void Method() { } }"; @@ -890,14 +890,14 @@ public async Task TestActiveDocumentSemanticModelCached() var project1 = solution.Projects.Single(); var document1 = project1.Documents.Single(); - // Without anything holding onto the semantic model, it should get releases. - var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + // Since this is the active document, we should hold onto it. + var objectReference = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); objectReference.AssertHeld(); } [Fact] - public async Task TestOnlyActiveDocumentSemanticModelCached() + public void TestOnlyActiveDocumentSemanticModelCached() { using var workspace = TestWorkspace.Create(""" @@ -924,8 +924,8 @@ class Program2 var document2 = project1.Documents.Last(); // Only the semantic model for the active document should be cached. - var objectReference1 = new ObjectReference(await document1.GetSemanticModelAsync()); - var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); objectReference1.AssertHeld(); objectReference2.AssertReleased(); From e49c5b6f1a70c95f6d1464480d78238de14cf3be Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:19:55 -0700 Subject: [PATCH 0613/1047] Add tesT --- .../Services/SolutionServiceTests.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index e8eb78cdbb173..f80e0bd521a6b 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -34,7 +34,7 @@ public class SolutionServiceTests FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() - => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); [Fact] public async Task TestCreation() @@ -932,7 +932,7 @@ class Program2 } [Fact] - public async Task TestActiveDocumentSemanticModelCached2() + public async Task TestRemoteWorkspaceCachesNothingIfActiveDocumentNotSynced() { var code = @"class Test { void Method() { } }"; @@ -944,17 +944,18 @@ public async Task TestActiveDocumentSemanticModelCached2() var project1 = solution.Projects.Single(); var document1 = project1.Documents.Single(); - var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); - - // This reference a project that doesn't exist. - // Ensure that it's still fine to get the checksum for this project we have. - project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); - - solution = project1.Solution; + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + + // The remote semantic model will not be held as it doesn't know what the active document is yet. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertReleased(); } private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) From 70832c78c93e7c2727711dbf9f6e17e4f00747de Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 9 Apr 2024 15:13:38 -0700 Subject: [PATCH 0614/1047] Implement MEF based pull diagnostics --- .../AlwaysActivateInProcLanguageClient.cs | 48 ++-- .../Portable/Diagnostics/DiagnosticKind.cs | 1 + .../Microsoft.CodeAnalysis.Features.csproj | 1 + ...ndContinueDiagnosticSource_OpenDocument.cs | 2 +- ...itAndContinueDiagnosticSource_Workspace.cs | 4 +- .../AbstractPullDiagnosticHandler.cs | 3 +- ...stractWorkspaceDiagnosticSourceProvider.cs | 207 ++++++++++++++++++ ...AbstractWorkspacePullDiagnosticsHandler.cs | 200 ++--------------- .../AbstractDocumentDiagnosticSource.cs | 2 +- .../AbstractProjectDiagnosticSource.cs | 14 +- ...stractWorkspaceDocumentDiagnosticSource.cs | 8 +- .../DiagnosticSourceManager.cs | 91 ++++++++ .../DocumentDiagnosticSource.cs | 7 +- ...ExportDiagnosticSourceProviderAttribute.cs | 20 ++ .../DiagnosticSources/IDiagnosticSource.cs | 4 +- .../IDiagnosticSourceManager.cs | 32 +++ .../IDiagnosticSourceProvider.cs | 34 +++ .../NonLocalDocumentDiagnosticSource.cs | 3 +- .../TaskListDiagnosticSource.cs | 2 +- .../DocumentDiagnosticSourceProvider.cs | 131 +++++++++++ .../DocumentPullDiagnosticHandler.cs | 170 ++++++-------- .../DocumentPullDiagnosticHandlerFactory.cs | 6 +- .../PublicDocumentDiagnosticSourceProvider.cs | 36 +++ ...licDocumentPullDiagnosticHandlerFactory.cs | 8 +- .../PublicDocumentPullDiagnosticsHandler.cs | 21 +- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 23 +- ...PublicWorkspaceDiagnosticSourceProvider.cs | 22 ++ ...icWorkspacePullDiagnosticHandlerFactory.cs | 5 +- .../PublicWorkspacePullDiagnosticsHandler.cs | 15 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 34 +++ .../WorkspaceDiagnosticSourceProvider.cs | 23 ++ .../WorkspacePullDiagnosticHandler.cs | 74 ++++--- .../WorkspacePullDiagnosticHandlerFactory.cs | 4 +- .../Contracts/IHotReloadDiagnosticService.cs | 18 ++ .../Internal/HotReloadDiagnosticService.cs | 29 +++ .../Internal/HotReloadDiagnosticSource.cs | 30 +++ .../HotReloadDiagnosticSourceProvider.cs | 55 +++++ .../InternalAPI.Unshipped.txt | 16 +- 38 files changed, 989 insertions(+), 414 deletions(-) create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index e4ce2a8eacb29..1eeb0980e9377 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; @@ -40,9 +41,11 @@ internal class AlwaysActivateInProcLanguageClient( ILspServiceLoggerFactory lspLoggerFactory, IThreadingContext threadingContext, ExportProvider exportProvider, + IDiagnosticSourceManager diagnosticSourceManager, [ImportMany] IEnumerable> buildOnlyDiagnostics) : AbstractInProcLanguageClient(lspServiceProvider, globalOptions, lspLoggerFactory, threadingContext, exportProvider) { private readonly ExperimentalCapabilitiesProvider _experimentalCapabilitiesProvider = defaultCapabilitiesProvider; + private readonly IDiagnosticSourceManager _diagnosticSourceManager = diagnosticSourceManager; private readonly IEnumerable> _buildOnlyDiagnostics = buildOnlyDiagnostics; protected override ImmutableArray SupportedLanguages => ProtocolConstants.RoslynLspLanguages; @@ -69,28 +72,35 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.SupportsDiagnosticRequests = true; serverCapabilities.DiagnosticProvider ??= new(); + + // VS does not distinguish between document and workspace diagnostics, so we need to merge them. + var diagnosticSourceNames = _diagnosticSourceManager.GetSourceNames(true) + .Concat(_diagnosticSourceManager.GetSourceNames(false)) + .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { SupportsMultipleContextsDiagnostics = true, - DiagnosticKinds = - [ - // Support a specialized requests dedicated to task-list items. This way the client can ask just - // for these, independently of other diagnostics. They can also throttle themselves to not ask if - // the task list would not be visible. - new(PullDiagnosticCategories.Task), - new(PullDiagnosticCategories.EditAndContinue), - // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. - new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic - // requests, allowing the former to quickly reach the user without blocking on the latter. In a - // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing - // the former to appear as soon as possible as they are much more critical for the user and should - // not be delayed by a slow analyzer. - new(PullDiagnosticCategories.DocumentCompilerSyntax), - new(PullDiagnosticCategories.DocumentCompilerSemantic), - new(PullDiagnosticCategories.DocumentAnalyzerSyntax), - new(PullDiagnosticCategories.DocumentAnalyzerSemantic), - ], + DiagnosticKinds = diagnosticSourceNames.Select(n => new VSInternalDiagnosticKind(n)).ToArray(), + //[ + // // Needs to be MEF driven + + // // Support a specialized requests dedicated to task-list items. This way the client can ask just + // // for these, independently of other diagnostics. They can also throttle themselves to not ask if + // // the task list would not be visible. + // new(PullDiagnosticCategories.Task), + // new(PullDiagnosticCategories.EditAndContinue), + // // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. + // new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), + // // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic + // // requests, allowing the former to quickly reach the user without blocking on the latter. In a + // // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing + // // the former to appear as soon as possible as they are much more critical for the user and should + // // not be delayed by a slow analyzer. + // new(PullDiagnosticCategories.DocumentCompilerSyntax), + // new(PullDiagnosticCategories.DocumentCompilerSemantic), + // new(PullDiagnosticCategories.DocumentAnalyzerSyntax), + // new(PullDiagnosticCategories.DocumentAnalyzerSemantic), + //], BuildOnlyDiagnosticIds = _buildOnlyDiagnostics .SelectMany(lazy => lazy.Metadata.BuildOnlyDiagnostics) .Distinct() diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs index bc97619a79475..92e43a3d0e51a 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs @@ -13,4 +13,5 @@ internal enum DiagnosticKind CompilerSemantic = 2, AnalyzerSyntax = 3, AnalyzerSemantic = 4, + EditAndContinue = 5, } diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index f06b4057c313c..0d983d125edbc 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs index 4f6b49fea36b6..deb7877c1b234 100644 --- a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs @@ -21,7 +21,7 @@ private sealed class OpenDocumentSource(Document document) : AbstractDocumentDia public override bool IsLiveSource() => true; - public override async Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { var designTimeDocument = Document; var designTimeSolution = designTimeDocument.Project.Solution; diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs index d74ca12db6421..b84fee77d2030 100644 --- a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs @@ -22,7 +22,7 @@ private sealed class ProjectSource(Project project, ImmutableArray true; - public override Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) => Task.FromResult(diagnostics); } @@ -31,7 +31,7 @@ private sealed class ClosedDocumentSource(TextDocument document, ImmutableArray< public override bool IsLiveSource() => true; - public override Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) => Task.FromResult(diagnostics); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 755e5d771299d..e0f1e9de55a38 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -295,7 +294,7 @@ private async Task ComputeAndReportCurrentDiagnosticsAsync( CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); - var diagnostics = await diagnosticSource.GetDiagnosticsAsync(DiagnosticAnalyzerService, context, cancellationToken).ConfigureAwait(false); + var diagnostics = await diagnosticSource.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); // If we can't get a text document identifier we can't report diagnostics for this source. // This can happen for 'fake' projects (e.g. used for TS script blocks). diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..1eedb04f10b46 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.TaskList; +using Roslyn.Utilities; + + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractWorkspaceDiagnosticSourceProvider( + IDiagnosticAnalyzerService diagnosticAnalyzerService, + IGlobalOptionService globalOptions, + ImmutableArray sourceNames) + : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public ImmutableArray SourceNames => sourceNames; + + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace + // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for + // document-diagnostics instead. + if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) + return []; + + if (sourceName == PullDiagnosticCategories.Task) + return GetTaskListDiagnosticSources(context, globalOptions); + + if (sourceName == PullDiagnosticCategories.EditAndContinue) + return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); + + // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). + if (sourceName == PullDiagnosticCategories.WorkspaceDocumentsAndProject) + return await GetDiagnosticSourcesAsync(context, globalOptions, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + // if it's a category we don't recognize, return nothing. + return []; + } + + private static ImmutableArray GetTaskListDiagnosticSources( + RequestContext context, IGlobalOptionService globalOptions) + { + Contract.ThrowIfNull(context.Solution); + + // Only compute task list items for closed files if the option is on for it. + var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; + if (!taskListEnabled) + return []; + + using var _ = ArrayBuilder.GetInstance(out var result); + + foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + { + foreach (var document in project.Documents) + { + if (!ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); + } + } + + return result.ToImmutableAndClear(); + } + + private static IEnumerable GetProjectsInPriorityOrder( + Solution solution, ImmutableArray supportedLanguages) + { + return GetProjectsInPriorityOrderWorker(solution) + .WhereNotNull() + .Distinct() + .Where(p => supportedLanguages.Contains(p.Language)); + + static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) + { + var documentTrackingService = solution.Services.GetRequiredService(); + + // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will + // prioritize the files from currently active projects, but then also include all other docs in all projects + // (depending on current FSA settings). + + var activeDocument = documentTrackingService.GetActiveDocument(solution); + var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); + + yield return activeDocument?.Project; + foreach (var doc in visibleDocuments) + yield return doc.Project; + + foreach (var project in solution.Projects) + yield return project; + } + } + + private static bool ShouldSkipDocument(RequestContext context, TextDocument document) + { + // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). + // Each handler treats those as separate worlds that they are responsible for. + if (context.IsTracking(document.GetURI())) + { + context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); + return true; + } + + // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics + // for any razor file they are interested in. + return document.IsRazorDocument(); + } + + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// + public static async ValueTask> GetDiagnosticSourcesAsync( + RequestContext context, IGlobalOptionService globalOptions, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + + using var _ = ArrayBuilder.GetInstance(out var result); + + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); + + foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + return result.ToImmutableAndClear(); + + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; + + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; + + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); + + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } + + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); + + return; + + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) + { + if (!ShouldSkipDocument(context, document)) + { + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); + } + } + } + + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); + } + + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } + } +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index f3911586aad23..c0872bae69c2d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -3,21 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.TaskList; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -27,6 +19,7 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler /// Flag that represents whether the LSP view of the world has changed. @@ -40,9 +33,11 @@ protected AbstractWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : base(diagnosticAnalyzerService, diagnosticRefresher, globalOptions) { + _diagnosticSourceManager = diagnosticSourceManager; _workspaceManager = workspaceManager; _workspaceRegistrationService = registrationService; @@ -56,32 +51,16 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override async ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace - // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for - // document-diagnostics instead. - if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) - return []; - - Contract.ThrowIfNull(context.Solution); - - var category = GetDiagnosticCategory(diagnosticsParams); - - // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 - - if (category == PullDiagnosticCategories.Task) - return GetTaskListDiagnosticSources(context, GlobalOptions); - - if (category == PullDiagnosticCategories.EditAndContinue) - return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); - - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); - - // if it's a category we don't recognize, return nothing. - return []; + if (GetDiagnosticCategory(diagnosticsParams) is string sourceName) + { + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); + } + else + { + return new([]); + } } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) @@ -115,159 +94,6 @@ protected override async Task WaitForChangesAsync(RequestContext context, Cancel return; } - private static ImmutableArray GetTaskListDiagnosticSources( - RequestContext context, IGlobalOptionService globalOptions) - { - Contract.ThrowIfNull(context.Solution); - - // Only compute task list items for closed files if the option is on for it. - var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; - if (!taskListEnabled) - return []; - - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) - { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } - } - - return result.ToImmutableAndClear(); - } - - /// - /// There are three potential sources for reporting workspace diagnostics: - /// - /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest - /// project snapshot and return up-to-date diagnostics computed from this analysis. - /// - /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly - /// triggered code analysis execution on either the current or a prior project snapshot, we return - /// diagnostics from this execution. These diagnostics may be stale with respect to the current - /// project snapshot, but they match user's intent of not enabling continuous background analysis - /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on - /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. - /// - /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. - /// - /// If full solution analysis is disabled AND code analysis was never executed for the given project, - /// we have no workspace diagnostics to report and bail out. - /// - public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(context.Solution); - - using var _ = ArrayBuilder.GetInstance(out var result); - - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); - - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); - - return result.ToImmutableAndClear(); - - async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; - - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); - - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } - - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); - - return; - - void AddDocumentSources(IEnumerable documents) - { - foreach (var document in documents) - { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } - } - } - - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; - } - } - - private static IEnumerable GetProjectsInPriorityOrder( - Solution solution, ImmutableArray supportedLanguages) - { - return GetProjectsInPriorityOrderWorker(solution) - .WhereNotNull() - .Distinct() - .Where(p => supportedLanguages.Contains(p.Language)); - - static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) - { - var documentTrackingService = solution.Services.GetRequiredService(); - - // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will - // prioritize the files from currently active projects, but then also include all other docs in all projects - // (depending on current FSA settings). - - var activeDocument = documentTrackingService.GetActiveDocument(solution); - var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); - - yield return activeDocument?.Project; - foreach (var doc in visibleDocuments) - yield return doc.Project; - - foreach (var project in solution.Projects) - yield return project; - } - } - - private static bool ShouldSkipDocument(RequestContext context, TextDocument document) - { - // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). - // Each handler treats those as separate worlds that they are responsible for. - if (context.IsTracking(document.GetURI())) - { - context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); - return true; - } - - // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics - // for any razor file they are interested in. - return document.IsRazorDocument(); - } - internal abstract TestAccessor GetTestAccessor(); internal readonly struct TestAccessor(AbstractWorkspacePullDiagnosticsHandler handler) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index 0de186f4e9ac1..f88810864a7bb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -18,7 +18,7 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do public abstract bool IsLiveSource(); public abstract Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Document.Id); public Project GetProject() => Document.Project; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index d34ef1d906826..b2efe993483c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -16,14 +16,14 @@ internal abstract class AbstractProjectDiagnosticSource(Project project) { protected Project Project => project; - public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(project, shouldIncludeAnalyzer); + public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(project, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(project, codeAnalysisService); public abstract bool IsLiveSource(); - public abstract Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + public abstract Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Project.Id); public Project GetProject() => Project; @@ -33,7 +33,8 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource(project) { /// /// This is a normal project source that represents live/fresh diagnostics that should supersede everything else. @@ -42,7 +43,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -55,7 +55,8 @@ public override async Task> GetDiagnosticsAsync( } } - private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) : AbstractProjectDiagnosticSource(project) + private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) + : AbstractProjectDiagnosticSource(project) { /// /// This source provides the results of the *last* explicitly kicked off "run code analysis" command from the @@ -66,7 +67,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..07f393928e27c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -12,13 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -28,7 +28,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -64,7 +63,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs new file mode 100644 index 0000000000000..664bd043fa3af --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +{ + [Export(typeof(DiagnosticSourceManager)), Shared] + internal class DiagnosticSourceManager : IDiagnosticSourceManager + { + private readonly Lazy> _sources; + private ImmutableDictionary>? _documentSources; + private ImmutableDictionary>? _workspaceSources; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] Lazy> sources) + { + _sources = sources; + } + + /// + public IEnumerable GetSourceNames(bool isDocument) + { + EnsureInitialized(); + return (isDocument ? _documentSources : _workspaceSources)!.Keys; + } + + /// + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + { + EnsureInitialized(); + var providersDictionary = isDocument ? _documentSources : _workspaceSources; + if (!providersDictionary!.TryGetValue(sourceName, out var providers)) + { + return []; + } + + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var provider in providers) + { + var sources = await provider.CreateDiagnosticSourcesAsync(context, sourceName, cancellationToken).ConfigureAwait(false); + builder.AddRange(sources); + } + + return builder.ToImmutableAndClear(); + } + + private void EnsureInitialized() + { + if (_documentSources == null || _workspaceSources == null) + { + Dictionary> documentSources = new(); + Dictionary> workspaceSources = new(); + foreach (var source in _sources.Value) + { + var attribute = source.GetType().GetCustomAttributes(inherit: false).FirstOrDefault(); + if (attribute != null) + { + var scopedSources = source.IsDocument ? documentSources : workspaceSources; + foreach (var sourceName in source.SourceNames) + { + if (!scopedSources.TryGetValue(sourceName, out var sources)) + { + sources = new List(); + scopedSources[sourceName] = sources; + } + ((List)sources).Add(source); + } + } + } + + var immutableSources = documentSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); + Interlocked.CompareExchange(ref _documentSources, immutableSources, null); + immutableSources = workspaceSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); + Interlocked.CompareExchange(ref _workspaceSources, immutableSources, null); + } + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 54f5c6ce5deb2..5cae97d9f3229 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -11,7 +11,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) + +internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; @@ -23,14 +24,14 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf // characteristics. GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs new file mode 100644 index 0000000000000..2c165e3d7ca29 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +/// +/// Use this attribute to declare a implementation for inclusion in a MEF-based workspace. +/// +/// +/// Declares a implementation for inclusion in a MEF-based workspace. +/// +[MetadataAttribute] +[AttributeUsage(AttributeTargets.Class)] +internal class ExportDiagnosticSourceProviderAttribute() : ExportAttribute(typeof(IDiagnosticSourceProvider)) +{ +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs index 34d110e4adb4c..242be19b8d8b6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -15,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// Wrapper around a source for diagnostics (e.g. a or ) /// so that we can share per file diagnostic reporting code in /// -internal interface IDiagnosticSource +internal interface IDiagnosticSource // we'll need one for XHR errors { Project GetProject(); ProjectOrDocumentId GetId(); @@ -33,7 +32,6 @@ internal interface IDiagnosticSource bool IsLiveSource(); Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs new file mode 100644 index 0000000000000..3246e5cc9153c --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources +{ + /// + /// Manages the diagnostic sources that provide diagnostics for the language server. + /// + internal interface IDiagnosticSourceManager + { + /// + /// Returns the names of all the sources that provide diagnostics for the given . + /// + /// True for document sources and false for workspace sources. + IEnumerable GetSourceNames(bool isDocument); + + /// + /// Creates the diagnostic sources for the given and . + /// + /// The context. + /// Source name. + /// True for document sources and false for workspace sources. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8c68d8b82094e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +/// +/// Provides diagnostic sources. +/// +internal interface IDiagnosticSourceProvider +{ + /// + /// True if this provider is for documents, false if it is for workspace. + /// + bool IsDocument { get; } + + /// + /// Source names that this provider can provide. + /// + ImmutableArray SourceNames { get; } + + /// + /// Creates the diagnostic sources for the given . + /// + /// The context. + /// Source name. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken); +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index 8ffd2d9b75fb9..d370f15240a60 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) +internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractDocumentDiagnosticSource(document) { private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -19,7 +19,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs index df8a3491ccdcc..fe6ebc182c6f9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs @@ -33,7 +33,7 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { var service = this.Document.GetLanguageService(); if (service == null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..e13833fe0dbe5 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : IDiagnosticSourceProvider +{ + private static readonly ImmutableArray sourceNames = + [ + PullDiagnosticCategories.Task, + PullDiagnosticCategories.DocumentCompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic + ]; + + public bool IsDocument => true; + public ImmutableArray SourceNames => sourceNames; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (sourceName == PullDiagnosticCategories.Task) + return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, globalOptions)); + + if (sourceName == PullDiagnosticCategories.EditAndContinue) + { + if (GetEditAndContinueDiagnosticSource(context) is IDiagnosticSource source) + { + return new([source]); + } + + return new([]); + } + + var diagnosticKind = sourceName switch + { + PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, + //// if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). + //null => DiagnosticKind.All, // !!!VS Code does not request any diag kind!!! + // // if it's a category we don't recognize, return nothing. + _ => (DiagnosticKind?)null, + }; + + if (diagnosticKind is null) + return new([]); + + return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind.Value, nonLocalDocumentDiagnostics: false, taskList: false, context, globalOptions)); + } + + internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) + => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; + + internal static ImmutableArray GetDiagnosticSources( + IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, bool nonLocalDocumentDiagnostics, bool taskList, RequestContext context, IGlobalOptionService globalOptions) + { + // For the single document case, that is the only doc we want to process. + // + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return []; + } + + var document = textDocument as Document; + if (taskList && document is null) + { + context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); + return []; + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return []; + } + + if (nonLocalDocumentDiagnostics) + return GetNonLocalDiagnosticSources(); + + return taskList + ? [new TaskListDiagnosticSource(document!, globalOptions)] + : [new DocumentDiagnosticSource(diagnosticAnalyzerService, diagnosticKind, textDocument)/*, Xaml source might go here; ???why doc while it should we workspace???*/]; + + ImmutableArray GetNonLocalDiagnosticSources() + { + Debug.Assert(!taskList); + + // This code path is currently only invoked from the public LSP handler, which always uses 'DiagnosticKind.All' + Debug.Assert(diagnosticKind == DiagnosticKind.All); + + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + return []; + + return [new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]; + + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index eace41f6259c5..401bbcb314cd3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -3,128 +3,90 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -[Method(VSInternalMethods.DocumentPullDiagnosticName)] -internal partial class DocumentPullDiagnosticHandler - : AbstractDocumentPullDiagnosticHandler +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, globalOptions) + [Method(VSInternalMethods.DocumentPullDiagnosticName)] + internal partial class DocumentPullDiagnosticHandler + : AbstractDocumentPullDiagnosticHandler { - } - - protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; + public DocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(analyzerService, diagnosticRefresher, globalOptions) + { + _diagnosticSourceManager = diagnosticSourceManager; + } - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.TextDocument; + protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; + + public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.TextDocument; + + protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalDiagnosticReport + { + Diagnostics = diagnostics, + ResultId = resultId, + Identifier = DocumentDiagnosticIdentifier, + // Mark these diagnostics as superseding any diagnostics for the same document from the + // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic + // values for a particular file, so our results should always be preferred over the workspace-pull + // values which are cached and may be out of date. + Supersedes = WorkspaceDiagnosticIdentifier, + } + ]; + + protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); + + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } - protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalDiagnosticReport + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) { - Diagnostics = diagnostics, - ResultId = resultId, - Identifier = DocumentDiagnosticIdentifier, - // Mark these diagnostics as superseding any diagnostics for the same document from the - // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic - // values for a particular file, so our results should always be preferred over the workspace-pull - // values which are cached and may be out of date. - Supersedes = WorkspaceDiagnosticIdentifier, + return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); } - ]; - protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + // The client didn't provide us with a previous result to look for, so we can't lookup anything. + return null; + } - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) - { - report = CreateReport(identifier, diagnostics: null, resultId); - return true; - } + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync( + VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); + if (diagnosticsParams.QueryingDiagnosticKind?.Value is string sourceName) + { + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + } + else + { + return new([]); + } } - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - return null; - } - - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) - => progress.GetFlattenedValues(); - - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) - => new(GetDiagnosticSource(diagnosticsParams, context) is { } diagnosticSource ? [diagnosticSource] : []); - - private IDiagnosticSource? GetDiagnosticSource(VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context) - { - var category = diagnosticsParams.QueryingDiagnosticKind?.Value; - - // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 - - if (category == PullDiagnosticCategories.Task) - return context.GetTrackedDocument() is { } document ? new TaskListDiagnosticSource(document, GlobalOptions) : null; - - if (category == PullDiagnosticCategories.EditAndContinue) - return GetEditAndContinueDiagnosticSource(context); - - var diagnosticKind = category switch + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - null => DiagnosticKind.All, - // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return null; - - return GetDiagnosticSource(diagnosticKind.Value, context); - } - - internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) - => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; - - internal static IDiagnosticSource? GetDiagnosticSource(DiagnosticKind diagnosticKind, RequestContext context) - => context.GetTrackedDocument() is { } textDocument ? new DocumentDiagnosticSource(diagnosticKind, textDocument) : null; - - internal static IDiagnosticSource? GetNonLocalDiagnosticSource(RequestContext context, IGlobalOptionService globalOptions) - { - var textDocument = context.GetTrackedDocument(); - if (textDocument == null) - return null; - - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return null; - - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - return new NonLocalDocumentDiagnosticSource(textDocument, shouldIncludeAnalyzer: static analyzer => !analyzer.IsCompilerAnalyzer()); + return progress.GetFlattenedValues(); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index d731fe70276cd..07a6cfcaabc17 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -14,6 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticsRefresher; private readonly IGlobalOptionService _globalOptions; @@ -21,14 +23,16 @@ internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticsRefresher = diagnosticsRefresher; _globalOptions = globalOptions; } public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticsRefresher, _globalOptions); + => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..1ef1c07135337 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : IDiagnosticSourceProvider +{ + public const string NonLocalSource = "nonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; + private static readonly ImmutableArray sourceNames = [NonLocalSource]; + + public bool IsDocument => true; + public ImmutableArray SourceNames => sourceNames; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + var nonLocalDocumentDiagnostics = sourceName == NonLocalSource; + var result = DocumentDiagnosticSourceProvider.GetDiagnosticSources(diagnosticAnalyzerService, DiagnosticKind.All, nonLocalDocumentDiagnostics, taskList: false, context, globalOptions); + return new(result); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index e369e4180ed43..3ae460c780e84 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -5,10 +5,9 @@ using System; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -19,6 +18,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticRefresher; private readonly IGlobalOptionService _globalOptions; @@ -26,10 +26,12 @@ internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFa [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public PublicDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticRefresher = diagnosticRefresher; _globalOptions = globalOptions; } @@ -37,6 +39,6 @@ public PublicDocumentPullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 400087cd41ef7..15e6062a31754 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -8,34 +8,35 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType; +using DocumentDiagnosticReport = SumType; [Method(Methods.TextDocumentDiagnosticName)] internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { private readonly string _nonLocalDiagnosticsSourceRegistrationId; private readonly IClientLanguageServerManager _clientLanguageServerManager; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : base(analyzerService, diagnosticsRefresher, globalOptions) { _nonLocalDiagnosticsSourceRegistrationId = Guid.NewGuid().ToString(); + _diagnosticSourceManager = diagnosticSourceManager; _clientLanguageServerManager = clientLanguageServerManager; } @@ -94,14 +95,10 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) { - var nonLocalDocumentDiagnostics = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString(); - - // Task list items are not reported through the public LSP diagnostic API. - var source = nonLocalDocumentDiagnostics - ? DocumentPullDiagnosticHandler.GetNonLocalDiagnosticSource(context, GlobalOptions) - : DocumentPullDiagnosticHandler.GetDiagnosticSource(DiagnosticKind.All, context); - - return new(source != null ? [source] : []); + var sourceName = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString() + ? PublicDocumentDiagnosticSourceProvider.NonLocalSource + : string.Empty; + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); } protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 940723c0e652a..bc4fa71d8804e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -28,26 +29,24 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. + var sources = _diagnosticSourceManager.GetSourceNames(isDocument: true); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() { - Registrations = - [ - new Registration - { - Id = _nonLocalDiagnosticsSourceRegistrationId, - Method = Methods.TextDocumentDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions - { - Identifier = DocumentNonLocalDiagnosticIdentifier.ToString() - } - } - ] + Registrations = sources.Select(FromSourceName).ToArray() }, cancellationToken).ConfigureAwait(false); } + Registration FromSourceName(string sourceName) + => new() + { + Id = sourceName, + Method = Methods.TextDocumentDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + }; + bool IsFsaEnabled() { foreach (var language in context.SupportedLanguages) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..d806d8e235ea0 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicWorkspaceDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, [""]) +{ +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index 90970a521fe53..9e11c966f21c4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -16,12 +17,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + var clientLanguageServerManager = lspServices.GetRequiredService(); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, clientLanguageServerManager, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 6c2beb5517041..6c72098ffaf41 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -19,15 +20,21 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed class PublicWorkspacePullDiagnosticsHandler( +internal sealed partial class PublicWorkspacePullDiagnosticsHandler: AbstractWorkspacePullDiagnosticsHandler, IDisposable +{ + private readonly IClientLanguageServerManager _clientLanguageServerManager; + public PublicWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, + IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticRefresher, globalOptions), IDisposable -{ + : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) + { + _clientLanguageServerManager = clientLanguageServerManager; + } /// /// Public API doesn't support categories (yet). diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs new file mode 100644 index 0000000000000..4ee7886ed524e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public +{ + internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitialized + { + public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); + await _clientLanguageServerManager.SendRequestAsync( + methodName: Methods.ClientRegisterCapabilityName, + @params: new RegistrationParams() + { + Registrations = sources.Select(FromSourceName).ToArray() + }, + cancellationToken).ConfigureAwait(false); + + Registration FromSourceName(string sourceName) + => new() + { + Id = sourceName, + Method = Methods.WorkspaceDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + }; + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..ca21d6c822fee --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, + [PullDiagnosticCategories.EditAndContinue, PullDiagnosticCategories.WorkspaceDocumentsAndProject]) +{ +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 66893cacf32fb..63e9e83e53495 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -17,52 +18,53 @@ internal sealed partial class WorkspacePullDiagnosticHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions) + workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { protected override string? GetDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalWorkspaceDiagnosticReport - { - TextDocument = identifier, - Diagnostics = diagnostics, - ResultId = resultId, - // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the - // same file produced by the DocumentPullDiagnosticHandler. - Identifier = WorkspaceDiagnosticIdentifier, - } - ]; +protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalWorkspaceDiagnosticReport + { + TextDocument = identifier, + Diagnostics = diagnostics, + ResultId = resultId, + // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the + // same file produced by the DocumentPullDiagnosticHandler. + Identifier = WorkspaceDiagnosticIdentifier, + } + ]; - protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); +protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) - { - // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of - // these and we can save a lot of memory not serializing/deserializing all of this. - report = null; - return false; - } - - protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); +protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) +{ + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; +} - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - { - // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics - // produced by document diagnostics. - return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); - } +protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); - protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) - { - return progress.GetFlattenedValues(); - } +protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) +{ + // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics + // produced by document diagnostics. + return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); +} - internal override TestAccessor GetTestAccessor() => new(this); +protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) +{ + return progress.GetFlattenedValues(); } + +internal override TestAccessor GetTestAccessor() => new(this); +} \ No newline at end of file diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index b928cf51f3910..7c13f4d46b74a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -16,12 +17,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal class WorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs new file mode 100644 index 0000000000000..9e55d78ba20e3 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + internal interface IHotReloadDiagnosticService + { + /// + /// Update the diagnostics for the given group name. + /// + /// The diagnostics. + /// The group name. + void UpdateDiagnostics(IEnumerable diagnostics, string groupName); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs new file mode 100644 index 0000000000000..6b8b08db21c92 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[Export(typeof(IHotReloadDiagnosticService)), Shared] +internal sealed class HotReloadDiagnosticService : IHotReloadDiagnosticService +{ + private readonly IHotReloadDiagnosticService? _implementation; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public HotReloadDiagnosticService([ImportMany] IEnumerable sourceProviders) + { + _implementation = sourceProviders.OfType().FirstOrDefault(); + } + + void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string groupName) + => _implementation?.UpdateDiagnostics(diagnostics, groupName); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..94cc82c6277f5 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal sealed class HotReloadDiagnosticSource(Project project, IDiagnosticsRefresher diagnosticsRefresher) : IDiagnosticSource +{ + Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => null; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(project.Id); + Project IDiagnosticSource.GetProject() => project; + bool IDiagnosticSource.IsLiveSource() => true; + string IDiagnosticSource.ToDisplayString() => project.Name; +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..7a575e5885cd7 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +// THIS IS WRONG. Need to follow EdinAndContinue +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class HotReloadDiagnosticSourceProvider(IDiagnosticsRefresher diagnosticsRefresher) + : IDiagnosticSourceProvider + , IHotReloadDiagnosticService +{ + private const string SourceName = "HotReloadDiagnostic"; + private static readonly ImmutableArray sourceNames = [SourceName]; + + private readonly ConcurrentDictionary projectDiagnostics = new(); + + bool IDiagnosticSourceProvider.IsDocument => false; + ImmutableArray IDiagnosticSourceProvider.SourceNames => sourceNames; + + void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string sourceName) + { + // TODO: store diagnostics in projectDiagnostics + //foreach (var diagnostic in diagnostics) + //{ + //} + diagnosticsRefresher.RequestWorkspaceRefresh(); + } + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (sourceName == SourceName) + { + return new(projectDiagnostics.Values.ToImmutableArray()); + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 504106533fe6b..8460dac45da33 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,15 +1,17 @@ +const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.SourceName = "XamlHotReloadDiagnostics" -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService.UpdateDiagnostics(System.Collections.Generic.IEnumerable! diagnostics, string! groupName) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsServiceBroker -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsServiceBroker.GetServiceBrokerAsync() -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.NotifyServiceBrokerInitialized.get -> Microsoft.CodeAnalysis.BrokeredServices.IOnServiceBrokerInitialized? -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.NotifyServiceBrokerInitialized.set -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.VisualDiagnosticsServiceBroker() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService.HotReloadDiagnosticService(System.Collections.Generic.IEnumerable! sourceProviders) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.XamlHotReloadDiagnosticSource(Microsoft.CodeAnalysis.Project! project, Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory From d5b9aaa7e984890c9a8d967bde68d1c62d4858fa Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:33:39 -0700 Subject: [PATCH 0615/1047] Add comment for max lambda binding value (#73050) --- src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs index 3c59e4b103324..498267c0fbd56 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs @@ -445,6 +445,10 @@ internal TResult BindWithLambdaBindingCountDiagnostics( 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) From 58c82d8e1a94b85f01bdbd2db6f02644dcea5b09 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:36:09 -0700 Subject: [PATCH 0616/1047] Fix test --- .../Services/SolutionServiceTests.cs | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index f80e0bd521a6b..35fc855463518 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -31,7 +32,7 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; public class SolutionServiceTests { private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = - FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); + FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -958,6 +959,69 @@ public async Task TestRemoteWorkspaceCachesNothingIfActiveDocumentNotSynced() objectReference2.AssertReleased(); } + [Theory, CombinatorialData] + public async Task TestRemoteWorkspaceCachesPropertyIfActiveDocumentIsSynced(bool updatePrimaryBranch) + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var remoteDocumentTrackingService = (RemoteDocumentTrackingService)remoteWorkspace.Services.GetRequiredService(); + remoteDocumentTrackingService.SetActiveDocument(document1.Id); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + // The remote semantic model will be held as it refers to the active document. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertHeld(); + } + + [Theory, CombinatorialData] + public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument(bool updatePrimaryBranch) + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); + + // By creating a checksum updater, we should notify the remote workspace of the active document. + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var checksumUpdater = new SolutionChecksumUpdater(workspace, listenerProvider, CancellationToken.None); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + var waiter = listenerProvider.GetWaiter(FeatureAttribute.SolutionChecksumUpdater); + await waiter.ExpeditedWaitAsync(); + + // The remote semantic model will be held as it refers to the active document. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertHeld(); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); From 837bc32add3c54a054e3f5a42ce161c6cef7dc3a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:09:20 -0700 Subject: [PATCH 0617/1047] Add test --- ...ActiveAndVisibleDocumentTrackingService.cs | 2 +- .../TestDocumentTrackingService.cs | 36 +++++++++ .../NavigateTo/AbstractNavigateToTests.cs | 1 + .../TestDocumentTrackingService.cs | 45 ----------- .../Services/SolutionServiceTests.cs | 80 ++++++++++++++++++- 5 files changed, 117 insertions(+), 47 deletions(-) rename src/EditorFeatures/TestUtilities/{Workspaces => DocumentTracking}/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs (97%) create mode 100644 src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs delete mode 100644 src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs diff --git a/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs similarity index 97% rename from src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs rename to src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs index 55fc88be78ef8..4f8ccd9d7cdc6 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Test.Utilities; +namespace Microsoft.CodeAnalysis.Editor.Test; internal sealed class FirstDocumentIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService { diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs new file mode 100644 index 0000000000000..f61704e592cb8 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.Test; + +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] +internal sealed class TestDocumentTrackingService : IDocumentTrackingService +{ + private DocumentId? _activeDocumentId; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestDocumentTrackingService() + { + } + + public event EventHandler? ActiveDocumentChanged; + + public void SetActiveDocument(DocumentId? newActiveDocumentId) + { + _activeDocumentId = newActiveDocumentId; + ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); + } + + public DocumentId? TryGetActiveDocument() + => _activeDocumentId; + + public ImmutableArray GetVisibleDocuments() + => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; +} diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index f494a0f9f0969..e318768462e40 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs deleted file mode 100644 index 91f7f5d0df0ec..0000000000000 --- a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Editor.Test -{ - [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestDocumentTrackingService : IDocumentTrackingService - { - private DocumentId? _activeDocumentId; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => true; - - public event EventHandler? ActiveDocumentChanged; - - public event EventHandler NonRoslynBufferTextChanged - { - add { } - remove { } - } - - public void SetActiveDocument(DocumentId? newActiveDocumentId) - { - _activeDocumentId = newActiveDocumentId; - ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); - } - - public DocumentId? TryGetActiveDocument() - => _activeDocumentId; - - public ImmutableArray GetVisibleDocuments() - => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; - } -} diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 35fc855463518..93583058aead3 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; @@ -31,8 +32,9 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; [Trait(Traits.Feature, Traits.Features.RemoteHost)] public class SolutionServiceTests { + private static readonly TestComposition s_composition = FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess); private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = - FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); + s_composition.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -1022,6 +1024,82 @@ public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument(bool upd objectReference2.AssertHeld(); } + [Theory, CombinatorialData] + public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument_EvenAcrossActiveDocumentChanges(bool updatePrimaryBranch) + { + using var workspace = TestWorkspace.Create(""" + + + + class Program1 + { + } + + + class Program2 + { + } + + + + """, composition: s_composition.AddParts(typeof(TestDocumentTrackingService))); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.First(); + var document2 = project1.Documents.Last(); + + // By creating a checksum updater, we should notify the remote workspace of the active document. Have it + // initially be set to the first document. + var documentTrackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); + documentTrackingService.SetActiveDocument(document1.Id); + + // Locally the semantic model for the first document will be held, but the second will not. + var objectReference1_step1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2_step1 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1_step1.AssertHeld(); + objectReference2_step1.AssertReleased(); + + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var checksumUpdater = new SolutionChecksumUpdater(workspace, listenerProvider, CancellationToken.None); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + var waiter = listenerProvider.GetWaiter(FeatureAttribute.SolutionChecksumUpdater); + await waiter.ExpeditedWaitAsync(); + + // The remote semantic model should match the local behavior once it has been notified that the first document is active. + var oopDocumentReference1_step1 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + var oopDocumentReference2_step1 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document2.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + oopDocumentReference1_step1.AssertHeld(); + oopDocumentReference2_step1.AssertReleased(); + + // Now, change the active document to the second document. + documentTrackingService.SetActiveDocument(document2.Id); + + // And get the semantic models again. The second document should now be held, and the first released. + var objectReference1_step2 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2_step2 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + + // The second document should be held. + objectReference2_step2.AssertHeld(); + + // Ensure that the active doc change is sync'ed to oop. + await waiter.ExpeditedWaitAsync(); + + // And get the semantic models again on the oop side. The second document should now be held, and the first released. + var oopDocumentReference1_step2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + var oopDocumentReference2_step2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document2.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + + // The second document on oop should now be held. + oopDocumentReference2_step2.AssertHeld(); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); From 5b1e9f4ce27df0bdfc28e888a9a245d39a023b5a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:17:01 -0700 Subject: [PATCH 0618/1047] Fix tests --- .../CSharpTest/NavigateTo/NavigateToSearcherTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 7092c2679be22..fca8d0d529356 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -7,8 +7,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; From f468e96071110c163d61c7cae23ef543c5bafe0f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:26:13 -0700 Subject: [PATCH 0619/1047] Apply suggestions from code review --- src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 53322e1d31de7..12b1fafbd9d8f 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -100,8 +100,7 @@ public void Shutdown() // Try to stop any work that is in progress. PauseWork(); - var trackingService = _workspace.Services.GetRequiredService(); - trackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; + _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; _workspace.WorkspaceChanged -= OnWorkspaceChanged; From 302392fabf425ce156d4e5b7377f31216ca5ebd2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:27:56 -0700 Subject: [PATCH 0620/1047] Primary constructor --- .../DocumentTracking/TestDocumentTrackingService.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs index f61704e592cb8..44fe5a20d19c0 100644 --- a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -10,16 +10,12 @@ namespace Microsoft.CodeAnalysis.Editor.Test; [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] -internal sealed class TestDocumentTrackingService : IDocumentTrackingService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class TestDocumentTrackingService() : IDocumentTrackingService { private DocumentId? _activeDocumentId; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentTrackingService() - { - } - public event EventHandler? ActiveDocumentChanged; public void SetActiveDocument(DocumentId? newActiveDocumentId) From d71f661297fa017f5159e358e142df80783eb02b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:31:34 -0700 Subject: [PATCH 0621/1047] Simplify --- .../Solution/Solution_SemanticModelCaching.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs index 970ed107c0b2f..32bebdc86d8d5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -30,17 +30,30 @@ public partial class Solution internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) { var service = this.Services.GetRequiredService(); + var activeDocumentId = service.TryGetActiveDocument(); - if (activeDocumentId != documentId) + if (activeDocumentId is null) + { + // no active document? then clear out any caches we have. + _activeDocumentSemanticModel = null; + _activeDocumentNullableDisabledSemanticModel = null; + } + else if (activeDocumentId != documentId) + { + // We have an active document, but we just obtained the semantic model for some other doc. Nothing to do + // here, we don't want to cache this. return; - - // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that - // other features that wake up for this active document are sure to be able to reuse the same one. -#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API - if (semanticModel.NullableAnalysisIsDisabled) - _activeDocumentNullableDisabledSemanticModel = semanticModel; + } else - _activeDocumentSemanticModel = semanticModel; + { + // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that + // other features that wake up for this active document are sure to be able to reuse the same one. +#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API + if (semanticModel.NullableAnalysisIsDisabled) + _activeDocumentNullableDisabledSemanticModel = semanticModel; + else + _activeDocumentSemanticModel = semanticModel; #pragma warning restore RSEXPERIMENTAL001 + } } } From 6f1271ae269bc8df587f7a3256461ebfd229a7c8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 16:37:29 -0700 Subject: [PATCH 0622/1047] Remove --- .../DocumentTracking/DefaultDocumentTrackingService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index 3832ac9faed5b..596cf877c78ab 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -14,8 +14,6 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingService { - public bool SupportsDocumentTracking => false; - public event EventHandler ActiveDocumentChanged { add { } remove { } } public ImmutableArray GetVisibleDocuments() From a8e7e877d50b7c63c82865b799d4691484bd62ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:03:00 -0700 Subject: [PATCH 0623/1047] Remove --- .../TestUtilities/NavigateTo/AbstractNavigateToTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index e318768462e40..d6a0a99528364 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -238,10 +238,7 @@ private class FirstDocIsVisibleDocumentTrackingService : IDocumentTrackingServic private FirstDocIsVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public DocumentId TryGetActiveDocument() => null; From 08a45dd579222659045e0fd8bf50defa043ded8c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:05:10 -0700 Subject: [PATCH 0624/1047] Remove --- .../VisualStudioActiveDocumentTracker.cs | 36 +--------------- ...ualStudioDocumentTrackingServiceFactory.cs | 41 ++++--------------- 2 files changed, 8 insertions(+), 69 deletions(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 83d642ae978f9..d71414b9d2b9a 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -7,20 +7,15 @@ using System.ComponentModel.Composition; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; @@ -35,7 +30,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents { private readonly IThreadingContext _threadingContext; - private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; /// /// The list of tracked frames. This can only be written by the UI thread, although can be read (with care) from any thread. @@ -51,11 +45,9 @@ internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioActiveDocumentTracker( IThreadingContext threadingContext, - [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider, - IVsEditorAdaptersFactoryService editorAdaptersFactoryService) + [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider) { _threadingContext = threadingContext; - _editorAdaptersFactoryService = editorAdaptersFactoryService; _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); @@ -84,11 +76,6 @@ public VisualStudioActiveDocumentTracker( /// public event EventHandler? DocumentsChanged; - /// - /// Raised when a non-Roslyn text buffer is edited, which can be used to back off of expensive background processing. May be raised on any thread. - /// - public event EventHandler? NonRoslynBufferTextChanged; - /// /// Returns the of the active document in a given . /// @@ -237,9 +224,6 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f TryInitializeTextBuffer(); } - private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventArgs e) - => _documentTracker.NonRoslynBufferTextChanged?.Invoke(_documentTracker, EventArgs.Empty); - /// /// Returns the current DocumentId for this window frame. Care must be made with this value, since "current" could change asynchronously as the document /// could be unregistered from a workspace. @@ -312,19 +296,6 @@ private void TryInitializeTextBuffer() } } - if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out var docData))) - { - if (docData is IVsTextBuffer bufferAdapter) - { - TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); - - if (TextBuffer != null && !TextBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) - { - TextBuffer.Changed += NonRoslynTextBuffer_Changed; - } - } - } - return; } @@ -333,11 +304,6 @@ private int Disconnect() _documentTracker._threadingContext.ThrowIfNotOnUIThread(); _documentTracker.RemoveFrame(this); - if (TextBuffer != null) - { - TextBuffer.Changed -= NonRoslynTextBuffer_Changed; - } - if (_frameEventsCookie != VSConstants.VSCOOKIE_NIL) { return ((IVsWindowFrame2)Frame).Unadvise(_frameEventsCookie); diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs index d2ef8a9aca10b..2273efa36396d 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs @@ -12,35 +12,21 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Host), Shared] -internal sealed class VisualStudioDocumentTrackingServiceFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioDocumentTrackingServiceFactory(VisualStudioActiveDocumentTracker activeDocumentTracker) : IWorkspaceServiceFactory { - private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioDocumentTrackingServiceFactory(VisualStudioActiveDocumentTracker activeDocumentTracker) - => _activeDocumentTracker = activeDocumentTracker; - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new VisualStudioDocumentTrackingService(_activeDocumentTracker, workspaceServices.Workspace); + => new VisualStudioDocumentTrackingService(activeDocumentTracker, workspaceServices.Workspace); - private class VisualStudioDocumentTrackingService : IDocumentTrackingService + private class VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker activeDocumentTracker, Workspace workspace) : IDocumentTrackingService { - private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker; - private readonly Workspace _workspace; - - public VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker activeDocumentTracker, Workspace workspace) - { - _activeDocumentTracker = activeDocumentTracker; - _workspace = workspace; - } - + private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker = activeDocumentTracker; + private readonly Workspace _workspace = workspace; private readonly object _gate = new(); private int _subscriptions = 0; private EventHandler? _activeDocumentChangedEventHandler; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add @@ -77,19 +63,6 @@ public event EventHandler ActiveDocumentChanged private void ActiveDocumentTracker_DocumentsChanged(object? sender, EventArgs e) => _activeDocumentChangedEventHandler?.Invoke(this, TryGetActiveDocument()); - public event EventHandler NonRoslynBufferTextChanged - { - add - { - _activeDocumentTracker.NonRoslynBufferTextChanged += value; - } - - remove - { - _activeDocumentTracker.NonRoslynBufferTextChanged -= value; - } - } - public DocumentId? TryGetActiveDocument() => _activeDocumentTracker.TryGetActiveDocument(_workspace); From 118a257185dccda12f0951f9c747a2fedd34e63c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:17:21 -0700 Subject: [PATCH 0625/1047] Simplify --- .../DocumentTracking/TestDocumentTrackingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs index 44fe5a20d19c0..ec01c8c69affc 100644 --- a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -28,5 +28,5 @@ public void SetActiveDocument(DocumentId? newActiveDocumentId) => _activeDocumentId; public ImmutableArray GetVisibleDocuments() - => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; + => _activeDocumentId != null ? [_activeDocumentId] : []; } From e2a1f8fe80789701f3b02bfa16d1b65d71c3b445 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 17 Apr 2024 17:28:17 -0700 Subject: [PATCH 0626/1047] Document deviation from member lookup standard (#72858) --- .../CSharp/Deviations from Standard.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) 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 { } +``` From b99374bf42345619c7e6d547d765acadf43a5cb4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:39:55 -0700 Subject: [PATCH 0627/1047] rename file --- ...ocumentTrackingService.cs => RemoteDocumentTrackingService.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Remote/ServiceHub/Services/{ServiceHubDocumentTrackingService.cs => RemoteDocumentTrackingService.cs} (100%) diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs similarity index 100% rename from src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs rename to src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs From 83bfccc4b1dd716a1af285121b83a8e3ffc7ada7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:41:25 -0700 Subject: [PATCH 0628/1047] simplify --- src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 12b1fafbd9d8f..805117ccedac8 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -82,7 +82,6 @@ public SolutionChecksumUpdater( // start listening workspace change event _workspace.WorkspaceChanged += OnWorkspaceChanged; - _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; if (_globalOperationService != null) @@ -101,7 +100,6 @@ public void Shutdown() PauseWork(); _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; - _workspace.WorkspaceChanged -= OnWorkspaceChanged; if (_globalOperationService != null) From 3dd59d6355c4d90359b94c47ba55aaf8b248c2e9 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 17 Apr 2024 18:51:51 -0700 Subject: [PATCH 0629/1047] Nullable analysis of conditional operator should use best common type unless target typed (#73047) --- .../Portable/Binder/Binder_Operators.cs | 7 +- .../Portable/FlowAnalysis/NullableWalker.cs | 3 +- .../Semantics/CollectionExpressionTests.cs | 203 ++++++++++++++++++ .../IOperationTests_IFixedStatement.cs | 49 +++-- 4 files changed, 230 insertions(+), 32 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 1b22a794af71d..6a69fe918bbfd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -4339,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 @@ -4353,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) @@ -4364,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/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index eb65524243bd8..91dc066983043 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -5867,7 +5867,7 @@ void makeAndAdjustReceiverSlot(BoundExpression receiver) TypeSymbol? resultType; bool wasTargetTyped = node is BoundConditionalOperator { WasTargetTyped: true }; - if (node.HasErrors || wasTargetTyped) + if (wasTargetTyped) { resultType = null; } @@ -5895,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 diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 41338fb37430b..87f26b8e0675b 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -37997,6 +37997,209 @@ static void Main() Diagnostic(ErrorCode.ERR_NoTypeDef, "[x, ..y]").WithArguments("MyCollectionA<>", $"{assemblyA}, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 35)); } + [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 IEnumerable F(bool b, MyCollection x, object y) + { + return b ? x : [y); + } + } + """; + + 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)); + + 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()); + } + + [Fact] + public void Nullable_SwitchExpression_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 F(bool b, MyCollection x, object y) + { + _ = b switch + { + true => x, + false => [y), + }; + } + } + """; + + 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)); + + 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] + 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 F(MyCollection x, object y) + { + _ = new[] { x, [y) }; + } + } + """; + + 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)); + + 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] + public void Nullable_LambdaExpression_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; + using System.Collections.Generic; + class Program + { + static void F(MyCollection x, object y) + { + var f = (bool b) => + { + if (b) return x; + return [y); + }; + } + } + """; + + 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)); + + 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] public void SynthesizedReadOnlyList_SingleElement() { 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)] From 06cc53f711f9b8b231efae45a83c50030b55ff8d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:04:32 -0700 Subject: [PATCH 0630/1047] Use 'nullable disabled' semantic model when computing FAR results --- .../FindReferences/FindReferenceCache.cs | 23 +++++++++++-------- .../FindReferencesDocumentState.cs | 6 ++--- .../FindReferencesSearchEngine.cs | 5 ++-- ...sSearchEngine_FindReferencesInDocuments.cs | 5 ++-- .../Core/Extensions/DocumentExtensions.cs | 15 ++++++++++++ 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 05efca6bffc64..e00a5fca66382 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -21,10 +21,15 @@ internal sealed class FindReferenceCache { private static readonly ConditionalWeakTable s_cache = new(); - public static FindReferenceCache GetCache(SemanticModel model) - => s_cache.GetValue(model, static model => new(model)); + public static async ValueTask GetCacheAsync(Document document, CancellationToken cancellationToken) + { + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid + // unnecessary costs while binding. + var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return s_cache.GetValue(model, static model => new(model)); + } - private readonly SemanticModel _semanticModel; + public readonly SemanticModel SemanticModel; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -34,7 +39,7 @@ public static FindReferenceCache GetCache(SemanticModel model) private FindReferenceCache(SemanticModel semanticModel) { - _semanticModel = semanticModel; + SemanticModel = semanticModel; _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, @@ -44,21 +49,19 @@ private FindReferenceCache(SemanticModel semanticModel) } public SymbolInfo GetSymbolInfo(SyntaxNode node, CancellationToken cancellationToken) - { - return _symbolInfoCache.GetOrAdd(node, static (n, arg) => arg._semanticModel.GetSymbolInfo(n, arg.cancellationToken), (_semanticModel, cancellationToken)); - } + => _symbolInfoCache.GetOrAdd(node, static (n, arg) => arg.SemanticModel.GetSymbolInfo(n, arg.cancellationToken), (SemanticModel, cancellationToken)); public IAliasSymbol? GetAliasInfo( ISemanticFactsService semanticFacts, SyntaxToken token, CancellationToken cancellationToken) { if (_aliasNameSet == null) { - var set = semanticFacts.GetAliasNameSet(_semanticModel, cancellationToken); + var set = semanticFacts.GetAliasNameSet(SemanticModel, cancellationToken); Interlocked.CompareExchange(ref _aliasNameSet, set, null); } if (_aliasNameSet.Contains(token.ValueText)) - return _semanticModel.GetAliasInfo(token.GetRequiredParent(), cancellationToken); + return SemanticModel.GetAliasInfo(token.GetRequiredParent(), cancellationToken); return null; } @@ -95,7 +98,7 @@ static async ValueTask> ComputeAndCacheTokensAsync( FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService(); - var root = await cache._semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var root = await cache.SemanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); // If the identifier was escaped in the file then we'll have to do a more involved search that actually // walks the root and checks all identifier tokens. diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index 59d513ef3bc73..bd5bf585dbfde 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -10,7 +10,6 @@ namespace Microsoft.CodeAnalysis.FindSymbols; internal class FindReferencesDocumentState( Document document, - SemanticModel semanticModel, SyntaxNode root, FindReferenceCache cache, HashSet? globalAliases) @@ -18,13 +17,14 @@ internal class FindReferencesDocumentState( private static readonly HashSet s_empty = []; public readonly Document Document = document; - public readonly SemanticModel SemanticModel = semanticModel; public readonly SyntaxNode Root = root; public readonly FindReferenceCache Cache = cache; public readonly HashSet GlobalAliases = globalAliases ?? s_empty; public readonly Solution Solution = document.Project.Solution; - public readonly SyntaxTree SyntaxTree = semanticModel.SyntaxTree; public readonly ISyntaxFactsService SyntaxFacts = document.GetRequiredLanguageService(); public readonly ISemanticFactsService SemanticFacts = document.GetRequiredLanguageService(); + + public SemanticModel SemanticModel => Cache.SemanticModel; + public SyntaxTree SyntaxTree => SemanticModel.SyntaxTree; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index b29348e91c29c..274bd87b4b2c6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -262,15 +262,14 @@ private async Task ProcessDocumentAsync( // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So // just grab those once here and hold onto them for the lifetime of this call. - var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var cache = FindReferenceCache.GetCache(model); + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState( - document, model, root, cache, globalAliases); + document, root, cache, globalAliases); await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 6c5dc334823e7..89cebc68d69a5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -87,14 +87,13 @@ async ValueTask PerformSearchInDocumentAsync( // appropriate finders checking this document for hits. We're likely going to need to perform syntax // and semantics checks in this file. So just grab those once here and hold onto them for the lifetime // of this call. - var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var cache = FindReferenceCache.GetCache(model); + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(document, model, root, cache, globalAliases); + var state = new FindReferencesDocumentState(document, root, cache, globalAliases); await PerformSearchInDocumentWorkerAsync(symbol, document, state).ConfigureAwait(false); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index 2d2c83b236f21..d7d5fa8da2911 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -41,6 +41,21 @@ public static async ValueTask GetRequiredSemanticModelAsync(this return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } +#if !CODE_STYLE + + public static async ValueTask GetRequiredNullableDisabledSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; + +#pragma warning disable RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + semanticModel = await document.GetSemanticModelAsync(SemanticModelOptions.DisableNullableAnalysis, cancellationToken).ConfigureAwait(false); +#pragma warning restore RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); + } + +#endif + public static async ValueTask GetRequiredSyntaxTreeAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSyntaxTree(out var syntaxTree)) From b9119f811b4387e339b6ea4bc491555c490af273 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:16:12 -0700 Subject: [PATCH 0631/1047] simplify --- .../FindReferences/FindReferenceCache.cs | 27 ++++++++++++++----- .../FindReferencesDocumentState.cs | 21 ++++++++------- ...sSearchEngine_FindReferencesInDocuments.cs | 7 +++-- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index e00a5fca66382..aaad258c3e4e3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -17,19 +17,32 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +/// +/// Caches information find-references needs associated with each document. Computed and cached so that multiple calls +/// to find-references in a row can share the same data. +/// internal sealed class FindReferenceCache { - private static readonly ConditionalWeakTable s_cache = new(); + private static readonly ConditionalWeakTable> s_cache = new(); public static async ValueTask GetCacheAsync(Document document, CancellationToken cancellationToken) { - // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid - // unnecessary costs while binding. - var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); - return s_cache.GetValue(model, static model => new(model)); + var lazy = s_cache.GetValue(document, static document => AsyncLazy.Create(ComputeCacheAsync, document)); + return await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false); + + static async Task ComputeCacheAsync(Document document, CancellationToken cancellationToken) + { + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid + // unnecessary costs while binding. + var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return new(document, model, root); + } } + public readonly Document Document; public readonly SemanticModel SemanticModel; + public readonly SyntaxNode Root; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -37,9 +50,11 @@ public static async ValueTask GetCacheAsync(Document documen private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; - private FindReferenceCache(SemanticModel semanticModel) + private FindReferenceCache(Document document, SemanticModel semanticModel, SyntaxNode root) { + Document = document; SemanticModel = semanticModel; + Root = root; _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index bd5bf585dbfde..c24f5caae2c8b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -8,23 +8,24 @@ namespace Microsoft.CodeAnalysis.FindSymbols; -internal class FindReferencesDocumentState( - Document document, - SyntaxNode root, +/// +/// Ephemeral information that find-references needs for a particular document when searching for a specific +/// symbol. Importantly, it contains the global aliases to that symbol within the current project. +internal sealed class FindReferencesDocumentState( FindReferenceCache cache, HashSet? globalAliases) { private static readonly HashSet s_empty = []; - public readonly Document Document = document; - public readonly SyntaxNode Root = root; public readonly FindReferenceCache Cache = cache; public readonly HashSet GlobalAliases = globalAliases ?? s_empty; - public readonly Solution Solution = document.Project.Solution; - public readonly ISyntaxFactsService SyntaxFacts = document.GetRequiredLanguageService(); - public readonly ISemanticFactsService SemanticFacts = document.GetRequiredLanguageService(); + public Document Document => this.Cache.Document; + public SyntaxNode Root => this.Cache.Root; + public SemanticModel SemanticModel => this.Cache.SemanticModel; + public SyntaxTree SyntaxTree => this.SemanticModel.SyntaxTree; - public SemanticModel SemanticModel => Cache.SemanticModel; - public SyntaxTree SyntaxTree => SemanticModel.SyntaxTree; + public Solution Solution => this.Document.Project.Solution; + public ISyntaxFactsService SyntaxFacts => this.Document.GetRequiredLanguageService(); + public ISemanticFactsService SemanticFacts => this.Document.GetRequiredLanguageService(); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 89cebc68d69a5..62263a56ebb80 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -87,20 +87,19 @@ async ValueTask PerformSearchInDocumentAsync( // appropriate finders checking this document for hits. We're likely going to need to perform syntax // and semantics checks in this file. So just grab those once here and hold onto them for the lifetime // of this call. - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(document, root, cache, globalAliases); + var state = new FindReferencesDocumentState(cache, globalAliases); - await PerformSearchInDocumentWorkerAsync(symbol, document, state).ConfigureAwait(false); + await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); } } async ValueTask PerformSearchInDocumentWorkerAsync( - ISymbol symbol, Document document, FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state) { // Always perform a normal search, looking for direct references to exactly that symbol. foreach (var finder in _finders) From c0317c33a958e434d7dc8e0e76bab402ccc1fa00 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:22:24 -0700 Subject: [PATCH 0632/1047] Simplify --- .../FindReferences/FindReferencesSearchEngine.cs | 7 +++---- ...FindReferencesSearchEngine_FindReferencesInDocuments.cs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 274bd87b4b2c6..871ac5bc3bbf0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -268,8 +268,8 @@ private async Task ProcessDocumentAsync( foreach (var symbol in symbols) { var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState( - document, root, cache, globalAliases); + var state = new FindReferencesDocumentState(cache, globalAliases); + await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); } } @@ -279,8 +279,7 @@ private async Task ProcessDocumentAsync( } async Task ProcessDocumentAsync( - ISymbol symbol, - FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 62263a56ebb80..0e982257b278e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -91,7 +91,7 @@ async ValueTask PerformSearchInDocumentAsync( foreach (var symbol in symbols) { - var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); + var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState(cache, globalAliases); await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); From 53046e24aeca419a77067283d8848818da5b5564 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:42:43 -0700 Subject: [PATCH 0633/1047] Use 'nullable disabled' semantic model when computing doc highlights results --- .../AbstractDocumentHighlightsService.cs | 4 +++- .../Core/Extensions/DocumentExtensions.cs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index 1671cca3ac7fd..f4075ce374447 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -63,7 +63,9 @@ public async Task> GetDocumentHighlightsAsync private async Task> GetDocumentHighlightsInCurrentProcessAsync( Document document, int position, IImmutableSet documentsToSearch, HighlightingOptions options, CancellationToken cancellationToken) { - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Document highlights are not impacted by nullable analysis. Get a semantic model with nullability disabled to + // lower the amount of work we need to do here. + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var result = TryGetEmbeddedLanguageHighlights(document, semanticModel, position, options, cancellationToken); if (!result.IsDefaultOrEmpty) return result; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index 2d2c83b236f21..d7d5fa8da2911 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -41,6 +41,21 @@ public static async ValueTask GetRequiredSemanticModelAsync(this return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } +#if !CODE_STYLE + + public static async ValueTask GetRequiredNullableDisabledSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; + +#pragma warning disable RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + semanticModel = await document.GetSemanticModelAsync(SemanticModelOptions.DisableNullableAnalysis, cancellationToken).ConfigureAwait(false); +#pragma warning restore RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); + } + +#endif + public static async ValueTask GetRequiredSyntaxTreeAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSyntaxTree(out var syntaxTree)) From 411356a688896e3779da325de226295f24d1d94d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:45:02 -0700 Subject: [PATCH 0634/1047] Use 'nullable disabled' semantic model when computing doc highlights results --- .../DocumentHighlighting/CSharpDocumentHighlightsService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index c8a48212c6e61..e872267221e35 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -56,7 +56,9 @@ protected override async Task> GetAdditionalReferencesA if (type.IsVar) { - semanticModel ??= await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Document highlights are not impacted by nullable analysis. Get a semantic model with nullability + // disabled to lower the amount of work we need to do here. + semanticModel ??= await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var boundSymbol = semanticModel.GetSymbolInfo(type, cancellationToken).Symbol; boundSymbol = boundSymbol?.OriginalDefinition; From 92cf500315e08c9a477083ee868498cba22a05e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:45:35 -0700 Subject: [PATCH 0635/1047] Wrap --- .../CSharpDocumentHighlightsService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index e872267221e35..e472dfb91502e 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -24,10 +24,12 @@ namespace Microsoft.CodeAnalysis.CSharp.DocumentHighlighting; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class CSharpDocumentHighlightsService( - [ImportMany] IEnumerable> services) : AbstractDocumentHighlightsService(LanguageNames.CSharp, - CSharpEmbeddedLanguagesProvider.Info, - CSharpSyntaxKinds.Instance, - services) + [ImportMany] IEnumerable> services) + : AbstractDocumentHighlightsService( + LanguageNames.CSharp, + CSharpEmbeddedLanguagesProvider.Info, + CSharpSyntaxKinds.Instance, + services) { protected override async Task> GetAdditionalReferencesAsync( Document document, ISymbol symbol, CancellationToken cancellationToken) From ba84fd8730ff9ffe5af1fb36cd541cdc12ee1171 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:51:00 -0700 Subject: [PATCH 0636/1047] fix --- .../FindSymbols/FindReferences/FindReferencesDocumentState.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index c24f5caae2c8b..7a2fb1f8c7d6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -11,6 +11,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols; /// /// Ephemeral information that find-references needs for a particular document when searching for a specific /// symbol. Importantly, it contains the global aliases to that symbol within the current project. +/// internal sealed class FindReferencesDocumentState( FindReferenceCache cache, HashSet? globalAliases) From 330c12eb3accf00c015eb3db499588f77038ae0e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 22:10:43 -0700 Subject: [PATCH 0637/1047] remove' git push --- .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 871ac5bc3bbf0..8d788e83130bb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -262,7 +262,6 @@ private async Task ProcessDocumentAsync( // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So // just grab those once here and hold onto them for the lifetime of this call. - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) From 635483895c5cd86d33be782aa05c2c89cd1061ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:02:29 -0700 Subject: [PATCH 0638/1047] Add test --- .../Syntax/Syntax/SyntaxEquivalenceTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs index 00f2a1223c791..73ee8135e6e75 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Linq; using Roslyn.Test.Utilities; using Xunit; @@ -1284,5 +1285,33 @@ void M() VerifyNotEquivalent(tree1, tree2, topLevel: false); VerifyEquivalent(tree1, tree2, topLevel: true); } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] + public void TestDeeplyNested() + { + var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); + + var tree1 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a) + { + var v = {{expr}}; + } + } + """"); + + var tree2 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a) + { + var v = {{expr}}; + } + } + """"); + + VerifyEquivalent(tree1, tree2, topLevel: false); + } } } From ce56b1ced2d3b7219ffe3e0d44052a5ac8ee1ae5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:04:37 -0700 Subject: [PATCH 0639/1047] add test --- .../Syntax/Syntax/SyntaxEquivalenceTests.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs index 73ee8135e6e75..4094cc28447d9 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs @@ -1287,7 +1287,35 @@ void M() } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] - public void TestDeeplyNested() + public void TestDeeplyNested1() + { + var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); + + var tree1 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a, int b, int c) + { + var v = {{expr}} + b; + } + } + """"); + + var tree2 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a, int b, int c) + { + var v = {{expr}} + c; + } + } + """"); + + VerifyNotEquivalent(tree1, tree2, topLevel: false); + } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] + public void TestDeeplyNested2() { var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); From 8f2659a3ae42afbab42bb6537b5c6873390ff76b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:49:26 -0700 Subject: [PATCH 0640/1047] remove unused event from interface that was never subscribed to --- .../NavigateTo/AbstractNavigateToTests.cs | 1 - ...ActiveAndVisibleDocumentTrackingService.cs | 1 - .../TestDocumentTrackingService.cs | 6 ----- .../DefaultDocumentTrackingService.cs | 1 - .../IDocumentTrackingService.cs | 5 ---- .../VisualStudioActiveDocumentTracker.cs | 26 +++---------------- ...ualStudioDocumentTrackingServiceFactory.cs | 13 ---------- .../ServiceHubDocumentTrackingService.cs | 1 - 8 files changed, 3 insertions(+), 51 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index b9f70d5b16fc5..619033a76de5a 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -240,7 +240,6 @@ private FirstDocIsVisibleDocumentTrackingService(Workspace workspace) public bool SupportsDocumentTracking => true; public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public DocumentId TryGetActiveDocument() => null; diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs index 08628f9c6e68e..7a02c1defd780 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs @@ -23,7 +23,6 @@ private FirstDocIsActiveAndVisibleDocumentTrackingService(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(); diff --git a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs index 91f7f5d0df0ec..de2466aa68453 100644 --- a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs @@ -24,12 +24,6 @@ public TestDocumentTrackingService() public event EventHandler? ActiveDocumentChanged; - public event EventHandler NonRoslynBufferTextChanged - { - add { } - remove { } - } - public void SetActiveDocument(DocumentId? newActiveDocumentId) { _activeDocumentId = newActiveDocumentId; diff --git a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs b/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs index 75022a16733cf..0c501f07d1f4e 100644 --- a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs @@ -22,7 +22,6 @@ 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/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs b/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs index 2e80613840883..1eafefc749af9 100644 --- a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs @@ -24,9 +24,4 @@ internal interface IDocumentTrackingService : IWorkspaceService 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/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 83d642ae978f9..828b7bd7e15d3 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -84,11 +84,6 @@ public VisualStudioActiveDocumentTracker( /// public event EventHandler? DocumentsChanged; - /// - /// Raised when a non-Roslyn text buffer is edited, which can be used to back off of expensive background processing. May be raised on any thread. - /// - public event EventHandler? NonRoslynBufferTextChanged; - /// /// Returns the of the active document in a given . /// @@ -237,9 +232,6 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f TryInitializeTextBuffer(); } - private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventArgs e) - => _documentTracker.NonRoslynBufferTextChanged?.Invoke(_documentTracker, EventArgs.Empty); - /// /// Returns the current DocumentId for this window frame. Care must be made with this value, since "current" could change asynchronously as the document /// could be unregistered from a workspace. @@ -312,17 +304,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; @@ -333,11 +318,6 @@ private int Disconnect() _documentTracker._threadingContext.ThrowIfNotOnUIThread(); _documentTracker.RemoveFrame(this); - if (TextBuffer != null) - { - TextBuffer.Changed -= NonRoslynTextBuffer_Changed; - } - if (_frameEventsCookie != VSConstants.VSCOOKIE_NIL) { return ((IVsWindowFrame2)Frame).Unadvise(_frameEventsCookie); diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs index d2ef8a9aca10b..8a03e1c78d83f 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs @@ -77,19 +77,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/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs index d3cbbf091565b..9f05d82f89f4f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -24,7 +24,6 @@ public ServiceHubDocumentTrackingService() public bool SupportsDocumentTracking => false; public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public ImmutableArray GetVisibleDocuments() { From 1037a132eab9b37bfb3fa1bee1399870a08be0ae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:52:05 -0700 Subject: [PATCH 0641/1047] fix --- .../Def/Workspace/VisualStudioActiveDocumentTracker.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 0dbf4b1f2e57f..73ead9d0ebedc 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -13,9 +13,11 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; @@ -30,6 +32,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents { private readonly IThreadingContext _threadingContext; + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; /// /// The list of tracked frames. This can only be written by the UI thread, although can be read (with care) from any thread. @@ -45,9 +48,11 @@ internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioActiveDocumentTracker( IThreadingContext threadingContext, - [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider) + [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider, + IVsEditorAdaptersFactoryService editorAdaptersFactoryService) { _threadingContext = threadingContext; + _editorAdaptersFactoryService = editorAdaptersFactoryService; _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); From a3ad10add75ef43fd657857d4709b1a63530114d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 00:51:58 -0700 Subject: [PATCH 0642/1047] Remove no longer used api --- ...faultUnitTestingDocumentTrackingService.cs | 31 -------- .../IUnitTestingDocumentTrackingService.cs | 30 -------- ...estingDocumentTrackingServiceExtensions.cs | 36 --------- ...or.AbstractUnitTestingPriorityProcessor.cs | 26 ------- ...UnitTestingIncrementalAnalyzerProcessor.cs | 3 - ...dinator.UnitTestingLowPriorityProcessor.cs | 7 +- ...ator.UnitTestingNormalPriorityProcessor.cs | 77 ------------------- 7 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/DefaultUnitTestingDocumentTrackingService.cs delete mode 100644 src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingService.cs delete mode 100644 src/Features/Core/Portable/ExternalAccess/UnitTesting/SolutionCrawler/IUnitTestingDocumentTrackingServiceExtensions.cs 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); From 1bf23610b41c148e1cf2d4144d0dbd6714b1f145 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Thu, 18 Apr 2024 11:36:24 -0700 Subject: [PATCH 0643/1047] Fix issue in editor config filepath comparison (#73033) This code previously just did a simple string.StartsWith to check if a file was in a folder. However, this would incorrectly return true for a case such as testing if c:\foo\test.txt was in folder c:\foo\test. --- .../Analyzers/AnalyzerConfigTests.cs | 22 +++++++++++++++++++ .../FileSystem/PathUtilitiesTests.cs | 21 ++++++++++++++++++ .../Portable/CommandLine/AnalyzerConfigSet.cs | 2 +- .../Core/Portable/FileSystem/PathUtilities.cs | 4 ++-- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs index d309e6fad88f4..0fb4efa394de5 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs @@ -1083,6 +1083,28 @@ public void MultipleEditorConfigs() }, options.Select(o => o.TreeOptions).ToArray()); } + [Fact] + public void FolderNamePrefixOfFileName() + { + var configs = ArrayBuilder.GetInstance(); + configs.Add(Parse(@" +[*.cs] +dotnet_diagnostic.cs000.severity = suggestion", "/root/.editorconfig")); + configs.Add(Parse(@" +root=true", "/root/test/.editorconfig")); + + var options = GetAnalyzerConfigOptions( + new[] { "/root/testing.cs" }, + configs); + configs.Free(); + + Assert.Equal(new[] + { + CreateImmutableDictionary( + ("cs000", ReportDiagnostic.Info)), + }, options.Select(o => o.TreeOptions).ToArray()); + } + [Fact] public void InheritOuterConfig() { diff --git a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs index c660ffa62b568..123f0ba58d5d1 100644 --- a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.IO; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -291,6 +292,26 @@ public void IsSameDirectoryOrChildOfNegativeTests() Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\B\C", @"C:\A\B\C\D")); } + [ConditionalFact(typeof(WindowsOnly))] + public void IsSameDirectoryOrChildOfSpecifyingCaseSensitivity_Windows() + { + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\a\B\C", @"C:\A\B", StringComparison.OrdinalIgnoreCase)); + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\b\C", @"C:\A\B", StringComparison.OrdinalIgnoreCase)); + + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\a\B\C", @"C:\A\B", StringComparison.Ordinal)); + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\b\C", @"C:\A\B", StringComparison.Ordinal)); + } + + [ConditionalFact(typeof(UnixLikeOnly))] + public void IsSameDirectoryOrChildOfSpecifyingCaseSensitivity_Unix() + { + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"/a/B/C", @"/A/B", StringComparison.OrdinalIgnoreCase)); + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"/A/b/C", @"/A/B", StringComparison.OrdinalIgnoreCase)); + + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"/a/B/C", @"/A/B", StringComparison.Ordinal)); + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"/A/b/C", @"/A/B", StringComparison.Ordinal)); + } + [Fact] public void IsValidFilePath() { diff --git a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs index eaa8de216fe95..2296b51ec33ce 100644 --- a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs +++ b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs @@ -204,7 +204,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) { var config = _analyzerConfigs[analyzerConfigIndex]; - if (normalizedPath.StartsWith(config.NormalizedDirectory, StringComparison.Ordinal)) + if (PathUtilities.IsSameDirectoryOrChildOf(normalizedPath, config.NormalizedDirectory, StringComparison.Ordinal)) { // If this config is a root config, then clear earlier options since they don't apply // to this source file. diff --git a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs index 0617a16e08dfd..570121ad3d3bb 100644 --- a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs @@ -159,7 +159,7 @@ public static string RemoveExtension(string path) return null; } - internal static bool IsSameDirectoryOrChildOf(string child, string parent) + internal static bool IsSameDirectoryOrChildOf(string child, string parent, StringComparison comparison = StringComparison.OrdinalIgnoreCase) { parent = RemoveTrailingDirectorySeparator(parent); string? currentChild = child; @@ -167,7 +167,7 @@ internal static bool IsSameDirectoryOrChildOf(string child, string parent) { currentChild = RemoveTrailingDirectorySeparator(currentChild); - if (currentChild.Equals(parent, StringComparison.OrdinalIgnoreCase)) + if (currentChild.Equals(parent, comparison)) { return true; } From 3aaabf34a3cf0ce18135e3433c525a6a08d86ada Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 11:45:28 -0700 Subject: [PATCH 0644/1047] Change how we yield --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index be74c5242f47f..af3f792840d2a 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -21,6 +21,7 @@ using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; @@ -191,7 +192,7 @@ private void OnTextViewClosed(object sender, EventArgs e) await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); var selection = TryGetCodeRefactoringSelection(state, range); - await Task.Yield().ConfigureAwait(false); + await TaskScheduler.Default; // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. var refactoringTask = selection != null @@ -210,7 +211,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task GetFixLevelAsync() { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await Task.Yield().ConfigureAwait(false); + await TaskScheduler.Default; var lowPriorityAnalyzers = new ConcurrentSet(); foreach (var order in Orderings) @@ -257,7 +258,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await Task.Yield().ConfigureAwait(false); + await TaskScheduler.Default; if (!selection.HasValue) { From 6b9ccdb03fe4b126a754c33e47547d5ef596a27a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 12:30:22 -0700 Subject: [PATCH 0645/1047] Move to loading text lazily --- .../Serialization/SerializableSourceText.cs | 33 +++++++++++++++++-- .../Remote/Core/AbstractAssetProvider.cs | 3 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 00c816d4c0e31..bf0beda4eaf3d 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -5,9 +5,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -190,4 +188,35 @@ public static SerializableSourceText Deserialize( contentHash); } } + + public TextLoader ToTextLoader(string? filePath) + => new SerializableSourceTextLoader(this, filePath); + + private sealed class SerializableSourceTextLoader : TextLoader + { + private readonly AsyncLazy _lazyTextAndVersion; + + public SerializableSourceTextLoader( + SerializableSourceText text, + string? filePath) + { + var version = VersionStamp.Create(); + + this.FilePath = filePath; + _lazyTextAndVersion = AsyncLazy.Create( + async static (tuple, cancellationToken) => + TextAndVersion.Create(await tuple.text.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), + static (tuple, cancellationToken) => + TextAndVersion.Create(tuple.text.GetText(cancellationToken), tuple.version, tuple.filePath), + (text, version, filePath)); + } + + internal override string? FilePath { get; } + + public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + => _lazyTextAndVersion.GetValueAsync(cancellationToken); + + internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) + => _lazyTextAndVersion.GetValue(cancellationToken); + } } diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 6a3c7c067f3ad..2cc7bf9e2192a 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -142,8 +142,7 @@ public async Task CreateDocumentInfoAsync( var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), attributeChecksum, cancellationToken).ConfigureAwait(false); var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), textChecksum, cancellationToken).ConfigureAwait(false); - var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - var textLoader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), attributes.FilePath)); + var textLoader = serializableSourceText.ToTextLoader(attributes.FilePath); // TODO: do we need version? return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); From 5dd739df0f0299ef362fdfa33ff738b7769d544b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 12:33:20 -0700 Subject: [PATCH 0646/1047] Flesh out more --- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 84b0ea0dd5915..5a44050959030 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -617,13 +617,14 @@ private async Task UpdateDocumentAsync( { var serializableSourceText = await _assetProvider.GetAssetAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); + var loader = serializableSourceText.ToTextLoader(document.FilePath); + var mode = PreservationMode.PreserveValue; document = document.Kind switch { - TextDocumentKind.Document => document.Project.Solution.WithDocumentText(document.Id, sourceText).GetDocument(document.Id)!, - TextDocumentKind.AnalyzerConfigDocument => document.Project.Solution.WithAnalyzerConfigDocumentText(document.Id, sourceText).GetAnalyzerConfigDocument(document.Id)!, - TextDocumentKind.AdditionalDocument => document.Project.Solution.WithAdditionalDocumentText(document.Id, sourceText).GetAdditionalDocument(document.Id)!, + TextDocumentKind.Document => document.Project.Solution.WithDocumentTextLoader(document.Id, loader, mode).GetRequiredDocument(document.Id), + TextDocumentKind.AnalyzerConfigDocument => document.Project.Solution.WithAnalyzerConfigDocumentTextLoader(document.Id, loader, mode).GetRequiredAnalyzerConfigDocument(document.Id), + TextDocumentKind.AdditionalDocument => document.Project.Solution.WithAdditionalDocumentTextLoader(document.Id, loader, mode).GetRequiredAdditionalDocument(document.Id), _ => throw ExceptionUtilities.UnexpectedValue(document.Kind), }; } From c2d66e812a97cc678ecf16579e027aa4ddc4ba7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:21:44 -0700 Subject: [PATCH 0647/1047] Docs --- .../Portable/Serialization/SerializableSourceText.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index bf0beda4eaf3d..ea5752f33ecfc 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -192,6 +192,15 @@ public static SerializableSourceText Deserialize( public TextLoader ToTextLoader(string? filePath) => new SerializableSourceTextLoader(this, filePath); + /// + /// A that wraps a and provides access to the text in + /// a deferred fashion. In practice, during a host and OOP sync, while all the documents will be 'serialized' over + /// to OOP, the actual contents of the documents will only need to be loaded depending on which files are open, and + /// thus what compilations and trees are needed. As such, we want to be able to lazily defer actually getting the + /// contents of the text until it's actually needed. This loader allows us to do that, allowing the OOP side to + /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually + /// realizing the real text values when they're needed. + /// private sealed class SerializableSourceTextLoader : TextLoader { private readonly AsyncLazy _lazyTextAndVersion; From ed66d54b21ea6959a63c1eb178c8fb52814752c7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:41:20 -0700 Subject: [PATCH 0648/1047] docs --- .../Serialization/SerializableSourceText.cs | 16 ++++++++++++++-- .../Solution/ConstantTextAndVersionSource.cs | 6 ++++++ .../Solution/DocumentState_TreeTextSource.cs | 6 ++++++ .../Workspace/Solution/ITextAndVersionSource.cs | 6 ++++++ .../Solution/LoadableTextAndVersionSource.cs | 8 ++++---- .../Solution/RecoverableTextAndVersion.cs | 6 ++++++ .../Solution/TextDocumentState_Checksum.cs | 4 +--- 7 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index ea5752f33ecfc..3ef976b2aec21 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -112,12 +112,22 @@ public SourceText GetText(CancellationToken cancellationToken) public static ValueTask FromTextDocumentStateAsync( TextDocumentState state, CancellationToken cancellationToken) { - if (state.Storage is TemporaryTextStorage storage) + if (state.TextAndVersionSource.TextLoader is SerializableSourceTextLoader serializableLoader) { - return new ValueTask(new SerializableSourceText(storage, storage.ContentHash)); + // If we're already pointing at a serializable loader, we can just use that directly. + return new(serializableLoader.Text); + } + else if (state.Storage is TemporaryTextStorage storage) + { + // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. + return new(new SerializableSourceText(storage, storage.ContentHash)); } else { + // Otherwise, the state object has reified the text into some other form, and dumped any original + // information on how it got it. In that case, we create a new text instance to represent the serializable + // source text out of. + return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( static (state, cancellationToken) => state.GetTextAsync(cancellationToken), static (text, _) => new SerializableSourceText(text, text.GetContentHash()), @@ -203,12 +213,14 @@ public TextLoader ToTextLoader(string? filePath) /// private sealed class SerializableSourceTextLoader : TextLoader { + public readonly SerializableSourceText Text; private readonly AsyncLazy _lazyTextAndVersion; public SerializableSourceTextLoader( SerializableSourceText text, string? filePath) { + Text = text; var version = VersionStamp.Create(); this.FilePath = filePath; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs index 32714a34a1c44..3313ec6f1c845 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs @@ -19,6 +19,12 @@ internal sealed class ConstantTextAndVersionSource(TextAndVersion value) : IText public bool CanReloadText => false; + /// + /// Not built from a text loader. + /// + public TextLoader? TextLoader + => null; + public TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken) => _value; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs index 30eaca390f9cb..42553e056311c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs @@ -22,6 +22,12 @@ private sealed class TreeTextSource(AsyncLazy textSource, VersionSta public bool CanReloadText => false; + /// + /// Not created from a text loader. + /// + public TextLoader? TextLoader + => null; + public async Task GetValueAsync(LoadTextOptions options, CancellationToken cancellationToken) { var text = await textSource.GetValueAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs index ab0bc21554dde..ac7f86ac6a84e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs @@ -16,6 +16,12 @@ internal interface ITextAndVersionSource /// bool CanReloadText { get; } + /// + /// Retrieves the underlying if that's what this was + /// created from and still has access to. + /// + TextLoader? TextLoader { get; } + bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value); TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken); Task GetValueAsync(LoadTextOptions options, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs index 70417d9b9dbad..28a1f3f6fe04f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs @@ -35,10 +35,10 @@ private sealed class LazyValueWithOptions(LoadableTextAndVersionSource source, L private WeakReference? _weakInstance; private Task LoadAsync(CancellationToken cancellationToken) - => Source.Loader.LoadTextAsync(Options, cancellationToken); + => Source.TextLoader.LoadTextAsync(Options, cancellationToken); private TextAndVersion LoadSynchronously(CancellationToken cancellationToken) - => Source.Loader.LoadTextSynchronously(Options, cancellationToken); + => Source.TextLoader.LoadTextSynchronously(Options, cancellationToken); public bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value) { @@ -102,13 +102,13 @@ private void UpdateWeakAndStrongReferences_NoLock(TextAndVersion textAndVersion) } } - public readonly TextLoader Loader = loader; + public TextLoader TextLoader { get; } = loader; public readonly bool CacheResult = cacheResult; private LazyValueWithOptions? _lazyValue; public bool CanReloadText - => Loader.CanReloadText; + => TextLoader.CanReloadText; private LazyValueWithOptions GetLazyValue(LoadTextOptions options) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index bcc498768b875..b4b327a618e44 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -42,6 +42,12 @@ private bool TryGetInitialSourceOrRecoverableText([NotNullWhen(true)] out ITextA return false; } + /// + /// Attempt to return the original loader if we still have it. + /// + public TextLoader? TextLoader + => (_initialSourceOrRecoverableText as ITextAndVersionSource)?.TextLoader; + public ITemporaryTextStorageInternal? Storage => (_initialSourceOrRecoverableText as RecoverableText)?.Storage; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs index 9c1513be2405e..d886e011e9112 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs @@ -36,11 +36,9 @@ private async Task ComputeChecksumsAsync(CancellationTok { using (Logger.LogBlock(FunctionId.DocumentState_ComputeChecksumsAsync, FilePath, cancellationToken)) { - var serializer = solutionServices.GetRequiredService(); - var infoChecksum = this.Attributes.Checksum; var serializableText = await SerializableSourceText.FromTextDocumentStateAsync(this, cancellationToken).ConfigureAwait(false); - var textChecksum = serializer.CreateChecksum(serializableText, cancellationToken); + var textChecksum = Checksum.Create(serializableText.ContentHash); return new DocumentStateChecksums(this.Id, infoChecksum, textChecksum); } From d9e1914b9d148c8fe5800e73faa675a3075a9e96 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:43:30 -0700 Subject: [PATCH 0649/1047] Rename --- .../Serialization/SerializableSourceText.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 3ef976b2aec21..aa83e25aa36c1 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -115,7 +115,7 @@ public static ValueTask FromTextDocumentStateAsync( if (state.TextAndVersionSource.TextLoader is SerializableSourceTextLoader serializableLoader) { // If we're already pointing at a serializable loader, we can just use that directly. - return new(serializableLoader.Text); + return new(serializableLoader.SerializableSourceText); } else if (state.Storage is TemporaryTextStorage storage) { @@ -213,23 +213,23 @@ public TextLoader ToTextLoader(string? filePath) /// private sealed class SerializableSourceTextLoader : TextLoader { - public readonly SerializableSourceText Text; + public readonly SerializableSourceText SerializableSourceText; private readonly AsyncLazy _lazyTextAndVersion; public SerializableSourceTextLoader( - SerializableSourceText text, + SerializableSourceText serializableSourceText, string? filePath) { - Text = text; + SerializableSourceText = serializableSourceText; var version = VersionStamp.Create(); this.FilePath = filePath; _lazyTextAndVersion = AsyncLazy.Create( async static (tuple, cancellationToken) => - TextAndVersion.Create(await tuple.text.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), + TextAndVersion.Create(await tuple.serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), static (tuple, cancellationToken) => - TextAndVersion.Create(tuple.text.GetText(cancellationToken), tuple.version, tuple.filePath), - (text, version, filePath)); + TextAndVersion.Create(tuple.serializableSourceText.GetText(cancellationToken), tuple.version, tuple.filePath), + (serializableSourceText, version, filePath)); } internal override string? FilePath { get; } From dd3177e009074ad03709609558a92ac35858271a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:59:31 -0700 Subject: [PATCH 0650/1047] Add back --- .../Core/Portable/Serialization/SerializableSourceText.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index aa83e25aa36c1..1293fe1eb62a0 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -14,6 +14,10 @@ using Roslyn.Utilities; using static Microsoft.CodeAnalysis.Host.TemporaryStorageService; +#if DEBUG +using System.Linq; +#endif + namespace Microsoft.CodeAnalysis.Serialization; #pragma warning disable CA1416 // Validate platform compatibility From fd77028539c5f8d4872fa29dc61af59e69fd6570 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:07:45 -0700 Subject: [PATCH 0651/1047] Simplify --- .../Portable/Serialization/SerializableSourceText.cs | 9 +++++++-- .../Core/Portable/Serialization/SerializerService.cs | 2 +- .../Workspace/Solution/TextDocumentState_Checksum.cs | 2 +- .../RemoteAssetSynchronizationService.cs | 5 +---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 1293fe1eb62a0..0a7bcf15f227c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -49,7 +49,7 @@ internal sealed class SerializableSourceText /// The hash that would be produced by calling on . Can be passed in when already known to avoid unnecessary computation costs. /// - public readonly ImmutableArray ContentHash; + private readonly ImmutableArray _contentHash; /// /// Weak reference to a SourceText computed from . Useful so that if multiple requests @@ -57,6 +57,11 @@ internal sealed class SerializableSourceText /// private readonly WeakReference _computedText = new(target: null); + /// + /// Checksum of the contents (see ) of the text. + /// + public Checksum ContentChecksum => Checksum.Create(_contentHash); + public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) : this(storage, text: null, contentHash) { @@ -73,7 +78,7 @@ private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, _storage = storage; _text = text; - ContentHash = contentHash; + _contentHash = contentHash; #if DEBUG var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 9d94bccc485e8..e263fdad2c23b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -84,7 +84,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken return CreateChecksum((AnalyzerReference)value, cancellationToken); case WellKnownSynchronizationKind.SerializableSourceText: - return Checksum.Create(((SerializableSourceText)value).ContentHash); + throw new InvalidOperationException("Clients can already get a checksum directly from a SerializableSourceText"); default: // object that is not part of solution is not supported since we don't know what inputs are required to diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs index d886e011e9112..3f646f09e1971 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs @@ -38,7 +38,7 @@ private async Task ComputeChecksumsAsync(CancellationTok { var infoChecksum = this.Attributes.Checksum; var serializableText = await SerializableSourceText.FromTextDocumentStateAsync(this, cancellationToken).ConfigureAwait(false); - var textChecksum = Checksum.Create(serializableText.ContentHash); + var textChecksum = serializableText.ContentChecksum; return new DocumentStateChecksums(this.Id, infoChecksum, textChecksum); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index f46c290d40e5d..c86c7482b169d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -56,8 +56,6 @@ public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextCh using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) { - var serializer = workspace.Services.GetRequiredService(); - // Try to get the text associated with baseTextChecksum var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); if (text == null) @@ -72,9 +70,8 @@ public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextCh // the entire document. var newText = text.WithChanges(textChanges); var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); - var newChecksum = serializer.CreateChecksum(newSerializableText, cancellationToken); - WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newSerializableText); + WorkspaceManager.SolutionAssetCache.GetOrAdd(newSerializableText.ContentChecksum, newSerializableText); } return; From 07ee0837209b65cefdf67e7cf31204ccf9ba2598 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:12:18 -0700 Subject: [PATCH 0652/1047] test checksums --- .../Core/Test.Next/Remote/SerializationValidator.cs | 2 +- .../Core/Test.Next/Remote/SnapshotSerializationTests.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 77cd8df2ccb65..cf37f02b4b2ae 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -189,7 +189,7 @@ internal async Task VerifyAssetAsync(Checksum attributeChecksum, Checksum textCh await VerifyAssetSerializationAsync( textChecksum, WellKnownSynchronizationKind.SerializableSourceText, - (v, k, s) => new SolutionAsset(s.CreateChecksum(v, CancellationToken.None), v)); + (v, k, s) => new SolutionAsset(v.ContentChecksum, v)); } internal async Task VerifyAssetSerializationAsync( diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index bbb5f577a5433..e06ebecbeef94 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -536,7 +536,7 @@ public async Task EmptyAssetChecksumTest() var serializer = document.Project.Solution.Services.GetService(); var text = await document.GetTextAsync().ConfigureAwait(false); - var source = serializer.CreateChecksum(new SerializableSourceText(text, text.GetContentHash()), CancellationToken.None); + var source = new SerializableSourceText(text, text.GetContentHash()).ContentChecksum; var metadata = serializer.CreateChecksum(new MissingMetadataReference(), CancellationToken.None); var analyzer = serializer.CreateChecksum(new AnalyzerFileReference(Path.Combine(TempRoot.Root, "missing"), new MissingAnalyzerLoader()), CancellationToken.None); @@ -693,7 +693,9 @@ private static SolutionAsset CloneAsset(ISerializerService serializer, SolutionA stream.Position = 0; using var reader = ObjectReader.TryGetReader(stream); var recovered = serializer.Deserialize(asset.Kind, reader, CancellationToken.None); - var assetFromStorage = new SolutionAsset(serializer.CreateChecksum(recovered, CancellationToken.None), recovered); + var checksum = recovered is SerializableSourceText text ? text.ContentChecksum : serializer.CreateChecksum(recovered, CancellationToken.None); + + var assetFromStorage = new SolutionAsset(checksum, recovered); Assert.Equal(asset.Checksum, assetFromStorage.Checksum); return assetFromStorage; From 9a1bbe2e0a74b8c9985243076c2c672d62bd0223 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 18 Apr 2024 14:33:37 -0700 Subject: [PATCH 0653/1047] Implement XHR IDiagnosticSource --- .../Protocol/Extensions/Extensions.cs | 2 - .../PublicDocumentPullDiagnosticsHandler.cs | 2 - .../Contracts/HotReloadDocumentDiagnostics.cs | 14 +++ .../Contracts/IHotReloadDiagnosticManager.cs | 28 ++++++ .../Contracts/IHotReloadDiagnosticService.cs | 18 ---- ...stractHotReloadDiagnosticSourceProvider.cs | 24 +++++ ...cumentHotReloadDiagnosticSourceProvider.cs | 39 ++++++++ .../Internal/HotReloadDiagnosticManager.cs | 95 +++++++++++++++++++ .../Internal/HotReloadDiagnosticService.cs | 29 ------ .../Internal/HotReloadDiagnosticSource.cs | 28 +++--- .../HotReloadDiagnosticSourceProvider.cs | 55 ----------- ...kspaceHotReloadDiagnosticSourceProvider.cs | 47 +++++++++ .../InternalAPI.Unshipped.txt | 31 ++++-- 13 files changed, 284 insertions(+), 128 deletions(-) create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 890759ebb9ceb..d9b8e4078f89f 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -10,8 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 15e6062a31754..9e1f3cfdd33c7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -23,7 +23,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [Method(Methods.TextDocumentDiagnosticName)] internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { - private readonly string _nonLocalDiagnosticsSourceRegistrationId; private readonly IClientLanguageServerManager _clientLanguageServerManager; private readonly IDiagnosticSourceManager _diagnosticSourceManager; @@ -35,7 +34,6 @@ public PublicDocumentPullDiagnosticsHandler( IGlobalOptionService globalOptions) : base(analyzerService, diagnosticsRefresher, globalOptions) { - _nonLocalDiagnosticsSourceRegistrationId = Guid.NewGuid().ToString(); _diagnosticSourceManager = diagnosticSourceManager; _clientLanguageServerManager = clientLanguageServerManager; } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs new file mode 100644 index 0000000000000..6f948ddfaef3d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray errors) + { + public DocumentId DocumentId => documentId; + public ImmutableArray Errors => errors; + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..aa48b2ea23197 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + internal interface IHotReloadDiagnosticManager + { + /// + /// Hot reload errors. + /// + ImmutableArray Errors { get; } + + /// + /// Update the diagnostics for the given group name. + /// + /// The diagnostics. + /// The group name. + void UpdateErrors(ImmutableArray errors, string groupName); + + /// + /// Clears all errors. + /// + void Clear(); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs deleted file mode 100644 index 9e55d78ba20e3..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts -{ - internal interface IHotReloadDiagnosticService - { - /// - /// Update the diagnostics for the given group name. - /// - /// The diagnostics. - /// The group name. - void UpdateDiagnostics(IEnumerable diagnostics, string groupName); - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..7c6a0e6915c9d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider +{ + internal const string SourceName = "HotReloadDiagnostic"; + internal static readonly ImmutableArray SourceNames = [SourceName]; + + ImmutableArray IDiagnosticSourceProvider.SourceNames => SourceNames; + bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + => throw new NotImplementedException(); + +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..46f3f36252622 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) + : AbstractHotReloadDiagnosticSourceProvider + , IDiagnosticSourceProvider +{ + bool IDiagnosticSourceProvider.IsDocument => true; + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } textDocument) + { + if (hotReloadErrorService.Errors.FirstOrDefault(e => e.DocumentId == textDocument.Id) is { } documentErrors) + { + return new([new HotReloadDiagnosticSource(textDocument, documentErrors.Errors)]); + } + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..14b54af628827 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[Export(typeof(IHotReloadDiagnosticManager)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class HotReloadDiagnosticManager(IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager +{ + private ImmutableDictionary> _errors = ImmutableDictionary>.Empty; + private ImmutableArray? _allErrors = null; + + void IHotReloadDiagnosticManager.UpdateErrors(ImmutableArray errors, string groupName) + { + errors = errors.RemoveAll(d => d.Errors.IsEmpty); + + var oldErrors = _errors; + if (errors.IsEmpty) + { + _errors = _errors.Remove(groupName); + } + else + { + _errors = _errors.SetItem(groupName, errors); + } + + if (_errors != oldErrors) + { + _allErrors = null; + diagnosticsRefresher.RequestWorkspaceRefresh(); + } + } + + void IHotReloadDiagnosticManager.Clear() + { + if (!_errors.IsEmpty) + { + _errors = ImmutableDictionary>.Empty; + _allErrors = ImmutableArray.Empty; + diagnosticsRefresher.RequestWorkspaceRefresh(); + } + } + + ImmutableArray IHotReloadDiagnosticManager.Errors + { + get + { + _allErrors ??= ComputeAllErrors(_errors); + return _allErrors.Value; + } + } + + private static ImmutableArray ComputeAllErrors(ImmutableDictionary> errors) + { + if (errors.Count == 0) + { + return ImmutableArray.Empty; + } + + if (errors.Count == 1) + { + return errors.First().Value; + } + + var allErrors = new Dictionary>(); + foreach (var group in errors.Values) + { + foreach (var documentErrors in group) + { + if (!allErrors.TryGetValue(documentErrors.DocumentId, out var list)) + { + list = new List(); + allErrors.Add(documentErrors.DocumentId, list); + } + + list.AddRange(documentErrors.Errors); + } + } + + return allErrors + .Where(kvp => kvp.Value.Count > 0) + .Select(kvp => new HotReloadDocumentDiagnostics(kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableArray(); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs deleted file mode 100644 index 6b8b08db21c92..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -[Export(typeof(IHotReloadDiagnosticService)), Shared] -internal sealed class HotReloadDiagnosticService : IHotReloadDiagnosticService -{ - private readonly IHotReloadDiagnosticService? _implementation; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public HotReloadDiagnosticService([ImportMany] IEnumerable sourceProviders) - { - _implementation = sourceProviders.OfType().FirstOrDefault(); - } - - void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string groupName) - => _implementation?.UpdateDiagnostics(diagnostics, groupName); -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index 94cc82c6277f5..c11487fd93070 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -5,26 +5,30 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Roslyn.LanguageServer.Protocol; +using Microsoft.CodeAnalysis.LanguageServer; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -internal sealed class HotReloadDiagnosticSource(Project project, IDiagnosticsRefresher diagnosticsRefresher) : IDiagnosticSource +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal { - Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + internal class HotReloadDiagnosticSource(TextDocument document, ImmutableArray errors) : IDiagnosticSource { - throw new NotImplementedException(); - } + Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var result = errors.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return Task.FromResult(result); + } - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => null; - ProjectOrDocumentId IDiagnosticSource.GetId() => new(project.Id); - Project IDiagnosticSource.GetProject() => project; - bool IDiagnosticSource.IsLiveSource() => true; - string IDiagnosticSource.ToDisplayString() => project.Name; + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); + Project IDiagnosticSource.GetProject() => document.Project; + bool IDiagnosticSource.IsLiveSource() => true; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index 7a575e5885cd7..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -// THIS IS WRONG. Need to follow EdinAndContinue -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class HotReloadDiagnosticSourceProvider(IDiagnosticsRefresher diagnosticsRefresher) - : IDiagnosticSourceProvider - , IHotReloadDiagnosticService -{ - private const string SourceName = "HotReloadDiagnostic"; - private static readonly ImmutableArray sourceNames = [SourceName]; - - private readonly ConcurrentDictionary projectDiagnostics = new(); - - bool IDiagnosticSourceProvider.IsDocument => false; - ImmutableArray IDiagnosticSourceProvider.SourceNames => sourceNames; - - void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string sourceName) - { - // TODO: store diagnostics in projectDiagnostics - //foreach (var diagnostic in diagnostics) - //{ - //} - diagnosticsRefresher.RequestWorkspaceRefresh(); - } - - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) - { - if (sourceName == SourceName) - { - return new(projectDiagnostics.Values.ToImmutableArray()); - } - - return new([]); - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..defca19ba4f9b --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) + : AbstractHotReloadDiagnosticSourceProvider + , IDiagnosticSourceProvider +{ + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (sourceName != SourceName || context.Solution is not Solution solution) + { + return new([]); + } + + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var documentErrors in hotReloadErrorService.Errors) + { + TextDocument? document = solution.GetAdditionalDocument(documentErrors.DocumentId) ?? solution.GetDocument(documentErrors.DocumentId); + if (document != null && !context.IsTracking(document.GetURI())) + { + builder.Add(new HotReloadDiagnosticSource(document, documentErrors.Errors)); + } + } + + var result = builder.ToImmutableAndClear(); + return new(result); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 8460dac45da33..9deff7bbdd06d 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,20 +1,31 @@ -const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.SourceName = "XamlHotReloadDiagnostics" -> string! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService.UpdateDiagnostics(System.Collections.Generic.IEnumerable! diagnostics, string! groupName) -> void +const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceName = "HotReloadDiagnostic" -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Errors.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Clear() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Errors.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.UpdateErrors(System.Collections.Immutable.ImmutableArray errors, string! groupName) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService.HotReloadDiagnosticService(System.Collections.Generic.IEnumerable! sourceProviders) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.XamlHotReloadDiagnosticSource(Microsoft.CodeAnalysis.Project! project, Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.AbstractHotReloadDiagnosticSourceProvider() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.CreateILspService(Microsoft.CodeAnalysis.LanguageServer.LspServices! lspServices, Microsoft.CodeAnalysis.LanguageServer.WellKnownLspServerKinds serverKind) -> Microsoft.CodeAnalysis.LanguageServer.ILspService! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void \ No newline at end of file +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void +static readonly Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceNames -> System.Collections.Immutable.ImmutableArray \ No newline at end of file From 0e493b6fb12f6b5c14853e8f68947208edb47d47 Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Thu, 18 Apr 2024 14:35:26 -0700 Subject: [PATCH 0654/1047] Hook up TS pull diagnostics with todos --- .../VSTypeScript/VSTypeScriptInProcLanguageClient.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index 78d641cebbfc2..6ad9d1bf4f913 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.LanguageServer.Client; @@ -57,6 +58,15 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.ProjectContextProvider = true; serverCapabilities.SupportsDiagnosticRequests = true; + serverCapabilities.DiagnosticProvider = new() + { + SupportsMultipleContextsDiagnostics = true, + DiagnosticKinds = + [ + new(PullDiagnosticCategories.Task), + ] + }; + return serverCapabilities; } From f8462e7f1b1a9a4a014ebc4b1ca7449c0bb7786e Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Thu, 18 Apr 2024 14:54:41 -0700 Subject: [PATCH 0655/1047] Add the other kinds --- .../VSTypeScript/VSTypeScriptInProcLanguageClient.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index 6ad9d1bf4f913..be675274d5530 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -64,6 +64,12 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa DiagnosticKinds = [ new(PullDiagnosticCategories.Task), + new(PullDiagnosticCategories.EditAndContinue), + new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), + new(PullDiagnosticCategories.DocumentCompilerSyntax), + new(PullDiagnosticCategories.DocumentCompilerSemantic), + new(PullDiagnosticCategories.DocumentAnalyzerSyntax), + new(PullDiagnosticCategories.DocumentAnalyzerSemantic), ] }; From d9229a70862181e38516914958c79c8250c13de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 18 Apr 2024 16:14:07 -0700 Subject: [PATCH 0656/1047] Allow WatchHotReloadService to change capabilities during session (#73066) --- .../Watch/Api/WatchHotReloadService.cs | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index d13938a57c7ad..d0762770efc95 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; @@ -14,12 +13,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; -internal sealed class WatchHotReloadService +internal sealed class WatchHotReloadService(SolutionServices services, Func>> capabilitiesProvider) { - private sealed class DebuggerService(ImmutableArray capabilities) : IManagedHotReloadService + private sealed class DebuggerService(Func>> capabilitiesProvider) : IManagedHotReloadService { - private readonly ImmutableArray _capabilities = capabilities; - public ValueTask> GetActiveStatementsAsync(CancellationToken cancellationToken) => ValueTaskFactory.FromResult(ImmutableArray.Empty); @@ -27,41 +24,39 @@ public ValueTask GetAvailabilityAsync(Guid module, => ValueTaskFactory.FromResult(new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.Available)); public ValueTask> GetCapabilitiesAsync(CancellationToken cancellationToken) - => ValueTaskFactory.FromResult(_capabilities); + => capabilitiesProvider(); public ValueTask PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) => ValueTaskFactory.CompletedTask; } - public readonly struct Update + public readonly struct Update( + Guid moduleId, + ImmutableArray ilDelta, + ImmutableArray metadataDelta, + ImmutableArray pdbDelta, + ImmutableArray updatedTypes, + ImmutableArray requiredCapabilities) { - public readonly Guid ModuleId; - public readonly ImmutableArray ILDelta; - public readonly ImmutableArray MetadataDelta; - public readonly ImmutableArray PdbDelta; - public readonly ImmutableArray UpdatedTypes; - public readonly ImmutableArray RequiredCapabilities; - - internal Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta, ImmutableArray updatedTypes, ImmutableArray requiredCapabilities) - { - ModuleId = moduleId; - ILDelta = ilDelta; - MetadataDelta = metadataDelta; - PdbDelta = pdbDelta; - UpdatedTypes = updatedTypes; - RequiredCapabilities = requiredCapabilities; - } + public readonly Guid ModuleId = moduleId; + public readonly ImmutableArray ILDelta = ilDelta; + public readonly ImmutableArray MetadataDelta = metadataDelta; + public readonly ImmutableArray PdbDelta = pdbDelta; + public readonly ImmutableArray UpdatedTypes = updatedTypes; + public readonly ImmutableArray RequiredCapabilities = requiredCapabilities; } private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); - private readonly IEditAndContinueService _encService; + private readonly IEditAndContinueService _encService = services.GetRequiredService().Service; + private DebuggingSessionId _sessionId; - private readonly ImmutableArray _capabilities; public WatchHotReloadService(HostWorkspaceServices services, ImmutableArray capabilities) - => (_encService, _capabilities) = (services.GetRequiredService().Service, capabilities); + : this(services.SolutionServices, () => ValueTaskFactory.FromResult(capabilities)) + { + } /// /// Starts the watcher. @@ -72,7 +67,7 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell { var newSessionId = await _encService.StartDebuggingSessionAsync( solution, - new DebuggerService(_capabilities), + new DebuggerService(capabilitiesProvider), NullPdbMatchingSourceTextProvider.Instance, captureMatchingDocuments: [], captureAllMatchingDocuments: true, @@ -82,6 +77,17 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell _sessionId = newSessionId; } + /// + /// Invoke when capabilities have changed. + /// + public void CapabilitiesChanged() + { + var sessionId = _sessionId; + Contract.ThrowIfFalse(sessionId != default, "Session has not started"); + + _encService.BreakStateOrCapabilitiesChanged(sessionId, inBreakState: null); + } + /// /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call or /// the one passed to for the first invocation. From 70351ce2a5eb3823baee495e330d9d651412cca3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:33:20 -0700 Subject: [PATCH 0657/1047] Do not dump --- .../Portable/Serialization/SerializableSourceText.cs | 9 +++++++++ .../Portable/Workspace/Solution/TextDocumentState.cs | 4 +++- .../Core/Portable/Workspace/Solution/TextLoader.cs | 10 ++++++++++ .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 9 ++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 0a7bcf15f227c..5b047c712cf1b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -243,6 +243,15 @@ async static (tuple, cancellationToken) => internal override string? FilePath { get; } + /// + /// Documents should always hold onto instances of this text loader strongly. In other words, they should load + /// from this, and then dump the contents into a RecoverableText object that then dumps the contents to a memory + /// mapped file within this process. Doing that is pointless as the contents of this text are already in a + /// memory mapped file on the host side. + /// + internal override bool AlwaysHoldStrongly + => true; + public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) => _lazyTextAndVersion.GetValueAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 33a799e996aba..0a8bbd9ffb828 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -60,7 +60,9 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L info.DocumentServiceProvider, info.Attributes, textAndVersionSource: info.TextLoader != null - ? CreateRecoverableText(info.TextLoader, solutionServices) + ? info.TextLoader.AlwaysHoldStrongly + ? CreateStrongText(info.TextLoader) + : CreateRecoverableText(info.TextLoader, solutionServices) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, encoding: null, loadTextOptions.ChecksumAlgorithm), VersionStamp.Default, info.FilePath)), loadTextOptions) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index e771796d35fac..9b4ab7c3e2d86 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -31,6 +31,16 @@ public abstract class TextLoader internal virtual string? FilePath => null; + /// + /// if the document that holds onto this loader should do so with a strong reference, versus + /// a reference that will take the contents of this loader and store them in a recoverable form. This should be + /// used when the underlying data is already stored in a recoverable form somewhere else and it would be wasteful to + /// store another copy. For example, a document that is backed by memory-mapped contents in another process does not + /// need to dump it's content to another memory-mapped file in the process it lives in. It can always recover the + /// text from the original process. + /// + internal virtual bool AlwaysHoldStrongly => false; + /// /// True if reloads from its original binary representation (e.g. file on disk). /// diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5a44050959030..9bf1cccabf9c4 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -618,7 +618,14 @@ private async Task UpdateDocumentAsync( var serializableSourceText = await _assetProvider.GetAssetAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var loader = serializableSourceText.ToTextLoader(document.FilePath); - var mode = PreservationMode.PreserveValue; + + // We strongly want to use identity-preservation with these loaders. By doing that we'll always use + // this loader as the 'truth' of how to get teh source-text for a particular document. When the + // text is first needed it will be pulled from the memory mapped files on the host side. Then, if + // that text is ever dropped, we'll still be pointing at this same loader. That will ensure that we + // still read the data from the host side, without doing something silly (like dumping it to our own + // memory mapped data on the OOP side). + var mode = PreservationMode.PreserveIdentity; document = document.Kind switch { From e97e662dec65af3b81be88ef5ed416701ffb50d2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:35:12 -0700 Subject: [PATCH 0658/1047] Defer loading more --- .../Serialization/SerializableSourceText.cs | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 5b047c712cf1b..14d89068abbb2 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -220,28 +220,14 @@ public TextLoader ToTextLoader(string? filePath) /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually /// realizing the real text values when they're needed. /// - private sealed class SerializableSourceTextLoader : TextLoader + private sealed class SerializableSourceTextLoader( + SerializableSourceText serializableSourceText, + string? filePath) : TextLoader { - public readonly SerializableSourceText SerializableSourceText; - private readonly AsyncLazy _lazyTextAndVersion; + public readonly SerializableSourceText SerializableSourceText = serializableSourceText; + private readonly VersionStamp _version = VersionStamp.Create(); - public SerializableSourceTextLoader( - SerializableSourceText serializableSourceText, - string? filePath) - { - SerializableSourceText = serializableSourceText; - var version = VersionStamp.Create(); - - this.FilePath = filePath; - _lazyTextAndVersion = AsyncLazy.Create( - async static (tuple, cancellationToken) => - TextAndVersion.Create(await tuple.serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), - static (tuple, cancellationToken) => - TextAndVersion.Create(tuple.serializableSourceText.GetText(cancellationToken), tuple.version, tuple.filePath), - (serializableSourceText, version, filePath)); - } - - internal override string? FilePath { get; } + internal override string? FilePath { get; } = filePath; /// /// Documents should always hold onto instances of this text loader strongly. In other words, they should load @@ -252,10 +238,10 @@ async static (tuple, cancellationToken) => internal override bool AlwaysHoldStrongly => true; - public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) - => _lazyTextAndVersion.GetValueAsync(cancellationToken); + public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + => TextAndVersion.Create(await this.SerializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), _version); internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) - => _lazyTextAndVersion.GetValue(cancellationToken); + => TextAndVersion.Create(this.SerializableSourceText.GetText(cancellationToken), _version); } } From 333c9660a0d0c476a068b9dc0b9b07a97741064f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:35:49 -0700 Subject: [PATCH 0659/1047] normal constructor --- .../Serialization/SerializableSourceText.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 14d89068abbb2..de9b534aa51ab 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -220,14 +220,20 @@ public TextLoader ToTextLoader(string? filePath) /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually /// realizing the real text values when they're needed. /// - private sealed class SerializableSourceTextLoader( - SerializableSourceText serializableSourceText, - string? filePath) : TextLoader + private sealed class SerializableSourceTextLoader : TextLoader { - public readonly SerializableSourceText SerializableSourceText = serializableSourceText; + public readonly SerializableSourceText SerializableSourceText; private readonly VersionStamp _version = VersionStamp.Create(); - internal override string? FilePath { get; } = filePath; + public SerializableSourceTextLoader( + SerializableSourceText serializableSourceText, + string? filePath) + { + SerializableSourceText = serializableSourceText; + FilePath = filePath; + } + + internal override string? FilePath { get; } /// /// Documents should always hold onto instances of this text loader strongly. In other words, they should load From a14cfa0bdec8fb56f90042373a05e70f3f01fd01 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 15:12:20 -0700 Subject: [PATCH 0660/1047] no dump semantics --- .../Workspace/Solution/TextDocumentState.cs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 0a8bbd9ffb828..1ec64287e0972 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -60,9 +60,7 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L info.DocumentServiceProvider, info.Attributes, textAndVersionSource: info.TextLoader != null - ? info.TextLoader.AlwaysHoldStrongly - ? CreateStrongText(info.TextLoader) - : CreateRecoverableText(info.TextLoader, solutionServices) + ? CreateTextFromLoader(info.TextLoader, PreservationMode.PreserveValue, solutionServices) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, encoding: null, loadTextOptions.ChecksumAlgorithm), VersionStamp.Default, info.FilePath)), loadTextOptions) { @@ -76,9 +74,6 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L private static ITextAndVersionSource CreateStrongText(TextAndVersion text) => new ConstantTextAndVersionSource(text); - private static ITextAndVersionSource CreateStrongText(TextLoader loader) - => new LoadableTextAndVersionSource(loader, cacheResult: true); - private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, SolutionServices services) { var service = services.GetRequiredService(); @@ -89,16 +84,6 @@ private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, : new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services); } - private static ITextAndVersionSource CreateRecoverableText(TextLoader loader, SolutionServices services) - { - var service = services.GetRequiredService(); - var options = service.Options; - - return options.DisableRecoverableText - ? CreateStrongText(loader) - : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), services); - } - public ITemporaryTextStorageInternal? Storage => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage; @@ -180,13 +165,21 @@ public TextDocumentState UpdateText(SourceText newText, PreservationMode mode) public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode) { // don't blow up on non-text documents. - var newTextSource = mode == PreservationMode.PreserveIdentity - ? CreateStrongText(loader) - : CreateRecoverableText(loader, solutionServices); + var newTextSource = CreateTextFromLoader(loader, mode, this.solutionServices); return UpdateText(newTextSource, mode, incremental: false); } + private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, PreservationMode mode, SolutionServices solutionServices) + { + var service = solutionServices.GetRequiredService(); + var options = service.Options; + + return mode == PreservationMode.PreserveIdentity || loader.AlwaysHoldStrongly || options.DisableRecoverableText + ? new LoadableTextAndVersionSource(loader, cacheResult: true) + : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); + } + protected virtual TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { return new TextDocumentState( From 1b188495072a32be7447e363a522258ba656f0d1 Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Thu, 18 Apr 2024 17:02:31 -0700 Subject: [PATCH 0661/1047] Remove extra kinds --- .../VSTypeScript/VSTypeScriptInProcLanguageClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index be675274d5530..cfc295a276a4d 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -64,10 +64,7 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa DiagnosticKinds = [ new(PullDiagnosticCategories.Task), - new(PullDiagnosticCategories.EditAndContinue), new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - new(PullDiagnosticCategories.DocumentCompilerSyntax), - new(PullDiagnosticCategories.DocumentCompilerSemantic), new(PullDiagnosticCategories.DocumentAnalyzerSyntax), new(PullDiagnosticCategories.DocumentAnalyzerSemantic), ] From 6dbaf63b5e24a6d88072453cfa2b7373fa23b121 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 18 Apr 2024 17:07:12 -0700 Subject: [PATCH 0662/1047] Cleanup binary version of clasp now that consumers have moved over --- Roslyn.sln | 8 ---- eng/config/PublishData.json | 1 - ...uageServerProtocol.Framework.Binary.csproj | 30 ------------- .../ExampleLanguageServer.cs | 2 +- .../AbstractHandlerProvider.cs | 4 -- .../AbstractLanguageServer.cs | 43 +++++-------------- .../AbstractLspLogger.cs | 4 -- .../AbstractRequestContextFactory.cs | 4 -- .../AbstractRequestScope.cs | 5 --- .../AbstractTelemetryService.cs | 4 -- .../HandlerProvider.cs | 2 +- .../Handlers/InitializeHandler.cs | 4 -- .../Handlers/InitializedHandler.cs | 4 -- .../IHandlerProvider.cs | 25 ----------- .../IInitializeManager.cs | 5 --- .../ILifeCycleManager.cs | 4 -- .../ILspLogger.cs | 5 --- .../ILspServices.cs | 5 --- .../IMethodHandler.cs | 4 -- .../INotificationHandler.cs | 8 ---- .../IQueueItem.cs | 4 -- .../IRequestContextFactory.cs | 42 ------------------ .../IRequestExecutionQueue.cs | 4 -- .../IRequestHandler.cs | 9 ---- .../ITextDocumentIdentifierHandler.cs | 8 ---- .../LanguageServerConstants.cs | 4 -- .../LanguageServerEndpointAttribute.cs | 4 -- ...eServerProtocol.Framework.Shared.projitems | 3 -- .../QueueItem.cs | 20 ++------- .../RequestExecutionQueue.cs | 17 -------- .../RequestHandlerMetadata.cs | 5 --- .../RequestShutdownEventArgs.cs | 5 --- .../WrappedHandlerProvider.cs | 30 ------------- .../Protocol/RoslynLanguageServer.cs | 10 ++--- .../Setup/Roslyn.VisualStudio.Setup.csproj | 6 --- 35 files changed, 21 insertions(+), 321 deletions(-) delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs diff --git a/Roslyn.sln b/Roslyn.sln index b54926c007207..848adc0f76206 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -547,8 +547,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Replay", "src\Tools\Replay\ EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CommonLanguageServerProtocol.Framework.Shared", "src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.shproj", "{64EADED3-4B5D-4431-BBE5-A4ABA1C38C00}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CommonLanguageServerProtocol.Framework.Binary", "src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework.Binary\Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj", "{730CADBA-701F-4722-9B6F-1FCC0DF2C95D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests", "src\Compilers\CSharp\Test\Emit3\Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj", "{4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SemanticSearch", "SemanticSearch", "{52ABB0E4-C3A1-4897-B51B-18EDA83F5D20}" @@ -1365,10 +1363,6 @@ Global {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Release|Any CPU.Build.0 = Release|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Release|Any CPU.Build.0 = Release|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1636,7 +1630,6 @@ Global {6D819E80-BA2F-4317-8368-37F8F4434D3A} = {8977A560-45C2-4EC2-A849-97335B382C74} {DB96C25F-39A9-4A6A-92BC-D1E42717308F} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} {64EADED3-4B5D-4431-BBE5-A4ABA1C38C00} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350} = {32A48625-F0AD-419D-828B-A50BDABA38EA} {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} @@ -1686,7 +1679,6 @@ Global src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{64eaded3-4b5d-4431-bbe5-a4aba1c38c00}*SharedItemsImports = 13 src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{686bf57e-a6ff-467b-aab3-44de916a9772}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 - src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{730cadba-701f-4722-9b6f-1fcc0df2c95d}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 6c568c4301a1c..ee01121b84fd6 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -89,7 +89,6 @@ "Microsoft.VisualStudio.LanguageServices.LiveShare": "vs-impl", "Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient": "vs-impl", "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl", - "Microsoft.CommonLanguageServerProtocol.Framework.Binary": "vs-impl" } }, "comment-about-servicing-branches": "For a list of VS versions under servicing, see https://docs.microsoft.com/en-us/visualstudio/releases/2019/servicing#support-options-for-enterprise-and-professional-customers", diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj deleted file mode 100644 index b82d6a2a340bd..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Library - netstandard2.0 - Microsoft.CommonLanguageServerProtocol.Framework - - - true - Microsoft.CommonLanguageServerProtocol.Framework.Binary - - A legacy binary implementation of Microsoft.CommonLanguageServerProtocol.Framework. - - - - BINARY_COMPAT - - - - - - - diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs index eb1381272b95e..4d6774ecd3ab3 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs @@ -27,7 +27,7 @@ protected override ILspServices ConstructLspServices() var serviceCollection = new ServiceCollection(); var _ = AddHandlers(serviceCollection) - .AddSingleton(_logger) + .AddSingleton(Logger) .AddSingleton, ExampleRequestContextFactory>() .AddSingleton(s => HandlerProvider) .AddSingleton, CapabilitiesManager>() diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs index 4af41c4bd96bc..7e36101ec4d64 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs @@ -13,11 +13,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Manages handler discovery and distribution. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractHandlerProvider -#else internal abstract class AbstractHandlerProvider -#endif { /// /// Gets the s for all registered methods. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs index f140ff52b78ee..8dddabcf31dbe 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs @@ -18,16 +18,10 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractLanguageServer -#else internal abstract class AbstractLanguageServer -#endif { private readonly JsonRpc _jsonRpc; -#pragma warning disable IDE1006 // Naming Styles - Required for API compat, TODO - https://github.com/dotnet/roslyn/issues/72251 - protected readonly ILspLogger _logger; -#pragma warning restore IDE1006 // Naming Styles + protected readonly ILspLogger Logger; protected readonly JsonSerializer _jsonSerializer; @@ -67,7 +61,7 @@ protected AbstractLanguageServer( JsonSerializer jsonSerializer, ILspLogger logger) { - _logger = logger; + Logger = logger; _jsonRpc = jsonRpc; _jsonSerializer = jsonSerializer; @@ -93,35 +87,20 @@ public void Initialize() /// This should only be called once, and then cached. protected abstract ILspServices ConstructLspServices(); - [Obsolete($"Use {nameof(HandlerProvider)} property instead.", error: false)] - protected virtual IHandlerProvider GetHandlerProvider() - { - var lspServices = _lspServices.Value; - var handlerProvider = new HandlerProvider(lspServices); - SetupRequestDispatcher(handlerProvider); - - return handlerProvider; - } - protected virtual AbstractHandlerProvider HandlerProvider { get { -#pragma warning disable CS0618 // Type or member is obsolete - var handlerProvider = GetHandlerProvider(); -#pragma warning restore CS0618 // Type or member is obsolete - if (handlerProvider is AbstractHandlerProvider abstractHandlerProvider) - { - return abstractHandlerProvider; - } - - return new WrappedHandlerProvider(handlerProvider); + var lspServices = _lspServices.Value; + var handlerProvider = new HandlerProvider(lspServices); + SetupRequestDispatcher(handlerProvider); + return handlerProvider; } } public ILspServices GetLspServices() => _lspServices.Value; - protected virtual void SetupRequestDispatcher(IHandlerProvider handlerProvider) + protected virtual void SetupRequestDispatcher(AbstractHandlerProvider handlerProvider) { var entryPointMethodInfo = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.ExecuteRequestAsync))!; // Get unique set of methods from the handler provider for the default language. @@ -187,7 +166,7 @@ public virtual void OnInitialized() protected virtual IRequestExecutionQueue ConstructRequestExecutionQueue() { var handlerProvider = HandlerProvider; - var queue = new RequestExecutionQueue(this, _logger, handlerProvider); + var queue = new RequestExecutionQueue(this, Logger, handlerProvider); queue.Start(); @@ -201,7 +180,7 @@ protected IRequestExecutionQueue GetRequestExecutionQueue() protected virtual string GetLanguageForRequest(string methodName, JToken? parameters) { - _logger.LogInformation($"Using default language handler for {methodName}"); + Logger.LogInformation($"Using default language handler for {methodName}"); return LanguageServerConstants.DefaultLanguageName; } @@ -328,7 +307,7 @@ async Task Shutdown_NoLockAsync(string message) // Immediately yield so that this does not run under the lock. await Task.Yield(); - _logger.LogInformation(message); + Logger.LogInformation(message); // Allow implementations to do any additional cleanup on shutdown. var lifeCycleManager = GetLspServices().GetRequiredService(); @@ -386,7 +365,7 @@ async Task Exit_NoLockAsync() } finally { - _logger.LogInformation("Exiting server"); + Logger.LogInformation("Exiting server"); _serverExitedSource.TrySetResult(null); } } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs index 41c4bf4001219..b849768f60027 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs @@ -9,11 +9,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractLspLogger : ILspLogger -#else internal abstract class AbstractLspLogger : ILspLogger -#endif { public abstract void LogDebug(string message, params object[] @params); public abstract void LogStartContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs index adcc85e1c484b..84849c484dc7a 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs @@ -21,11 +21,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// /// The type of the RequestContext to be used by the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractRequestContextFactory -#else internal abstract class AbstractRequestContextFactory -#endif { /// /// Create a object from the given . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs index 7e5ea5c8aeb5a..27762af48782d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractRequestScope(string name) : IDisposable -#else internal abstract class AbstractRequestScope(string name) : IDisposable -#endif { public string Name { get; } = name; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs index 7df8edce2f545..c5ccdf85ab942 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs @@ -7,11 +7,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractTelemetryService -#else internal abstract class AbstractTelemetryService -#endif { public abstract AbstractRequestScope CreateRequestScope(string lspMethodName); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs index ac0e0bd0ad03b..93d0b2f64e2b5 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs @@ -15,7 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// -internal class HandlerProvider : AbstractHandlerProvider, IHandlerProvider +internal class HandlerProvider : AbstractHandlerProvider { private readonly ILspServices _lspServices; private ImmutableDictionary>? _requestHandlers; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs index aeb2a3d698f06..564898a683de1 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs @@ -11,11 +11,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialize", LanguageServerConstants.DefaultLanguageName)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class InitializeHandler -#else internal class InitializeHandler -#endif : IRequestHandler { private readonly IInitializeManager _capabilitiesManager; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs index 46f87a4c52df4..d4a14851e4a70 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs @@ -12,11 +12,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialized", LanguageServerConstants.DefaultLanguageName)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class InitializedHandler : INotificationHandler -#else internal class InitializedHandler : INotificationHandler -#endif { private bool HasBeenInitialized = false; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs deleted file mode 100644 index 837cd4b051de5..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// Manages handler discovery and distribution. -/// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IHandlerProvider -#else -internal interface IHandlerProvider -#endif -{ - ImmutableArray GetRegisteredMethods(); - - IMethodHandler GetMethodHandler(string method, Type? requestType, Type? responseType); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs index 13e9fc9293a3e..2a73d500c0919 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs @@ -6,12 +6,7 @@ #nullable enable namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IInitializeManager -#else internal interface IInitializeManager -#endif { /// /// Gets a response to be used for "initialize", completing the negoticaitons between client and server. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs index 73b17b89df6ad..b8f704c68d15f 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs @@ -13,11 +13,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An optional component to run additional logic when LSP shutdown and exit are called, /// for example logging messages, cleaning up custom resources, etc. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILifeCycleManager -#else internal interface ILifeCycleManager -#endif { /// /// Called when the server recieves the LSP exit notification. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs index 3bfe075fc3e0e..e7ac139a4ddc2 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILspLogger -#else internal interface ILspLogger -#endif { void LogStartContext(string message, params object[] @params); void LogEndContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs index 14ed351fb7e7c..7b094ed23ea64 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs @@ -10,12 +10,7 @@ using System.Collections.Immutable; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILspServices : IDisposable -#else internal interface ILspServices : IDisposable -#endif { T GetRequiredService() where T : notnull; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs index d8299e27250a1..6c637662c618d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs @@ -10,11 +10,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Top level type for LSP request handler. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IMethodHandler -#else internal interface IMethodHandler -#endif { /// /// Whether or not the solution state on the server is modified as a part of handling this request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs index 22c8d8f2c38a1..513a78dcc9c70 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs @@ -14,11 +14,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An interface for handlers of methods which do not return a response and receive no parameters. /// /// The type of the RequestContext to be used by this handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface INotificationHandler : IMethodHandler -#else internal interface INotificationHandler : IMethodHandler -#endif { Task HandleNotificationAsync(TRequestContext requestContext, CancellationToken cancellationToken); } @@ -28,11 +24,7 @@ internal interface INotificationHandler : IMethodHandler /// /// The type of the Request parameter to be received. /// The type of the RequestContext to be used by this handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface INotificationHandler : IMethodHandler -#else internal interface INotificationHandler : IMethodHandler -#endif { Task HandleNotificationAsync(TRequest request, TRequestContext requestContext, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs index 832bbf4fdeae2..23f1ea4cd188e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs @@ -15,11 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An item to be queued for execution. /// /// The type of the request context to be passed along to the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IQueueItem -#else internal interface IQueueItem -#endif { /// /// Executes the work specified by this queue item. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs deleted file mode 100644 index ee07e97cfdc0a..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// -/// A factory for creating objects from 's. -/// -/// -/// RequestContext's are useful for passing document context, since by default -/// is run on the queue thread (and thus no mutating requests may be executing simultaneously, preventing possible race conditions). -/// It also allows somewhere to pass things like the or which are useful on a wide variety of requests. -/// -/// -/// The type of the RequestContext to be used by the handler. -[Obsolete($"Use {nameof(AbstractRequestContextFactory)} instead.", error: false)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestContextFactory -#else -internal interface IRequestContextFactory -#endif -{ - /// - /// Create a object from the given . - /// Note - throwing in the implementation of this method will cause the server to shutdown. - /// - /// The from which to create a request. - /// The request parameters. - /// - /// The for this request. - /// This method is called on the queue thread to allow context to be retrieved serially, without the possibility of race conditions from Mutating requests. - Task CreateRequestContextAsync(IQueueItem queueItem, TRequestParam requestParam, CancellationToken cancellationToken); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs index 1268202d4328f..463e597c70cf3 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs @@ -15,11 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// Queues requests to be executed in the proper order. /// /// The type of the RequestContext to be used by the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestExecutionQueue : IAsyncDisposable -#else internal interface IRequestExecutionQueue : IAsyncDisposable -#endif { /// /// Queue a request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs index 2bc711aabe3a2..7d58b68427183 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs @@ -9,12 +9,7 @@ using System.Threading.Tasks; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestHandler : IMethodHandler -#else internal interface IRequestHandler : IMethodHandler -#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. @@ -26,11 +21,7 @@ internal interface IRequestHandler : IMeth Task HandleRequestAsync(TRequest request, TRequestContext context, CancellationToken cancellationToken); } -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestHandler : IMethodHandler -#else internal interface IRequestHandler : IMethodHandler -#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs index eaf30967bc0b9..8c746ab34f188 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs @@ -7,11 +7,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler -#else internal interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler -#endif { /// /// Gets the identifier of the document from the request, if the request provides one. @@ -19,10 +15,6 @@ internal interface ITextDocumentIdentifierHandler /// Default language name for use with and . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs index 1f8c71ce2d7ab..84b972359ce00 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs @@ -14,11 +14,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An attribute which identifies the method which an implements. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class LanguageServerEndpointAttribute : Attribute -#else internal class LanguageServerEndpointAttribute : Attribute -#endif { /// /// Contains the method that this implements. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems index 3e94a73112030..4f299de12be5e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems @@ -18,7 +18,6 @@ - @@ -26,7 +25,6 @@ - @@ -36,7 +34,6 @@ - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs index 5cf8e93cc2383..31dff9c3b2b81 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs @@ -86,30 +86,16 @@ public static (IQueueItem, Task) Create( return (queueItem, queueItem._completionSource.Task); } -#pragma warning disable CS0618 // Type or member is obsolete public async Task CreateRequestContextAsync(IMethodHandler handler, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _requestTelemetryScope?.RecordExecutionStart(); - var requestContextFactory = (AbstractRequestContextFactory?)LspServices.TryGetService(typeof(AbstractRequestContextFactory)); - if (requestContextFactory is not null) - { - var context = await requestContextFactory.CreateRequestContextAsync(this, handler, _request, cancellationToken).ConfigureAwait(false); - return context; - } - - var obsoleteContextFactory = (IRequestContextFactory?)LspServices.TryGetService(typeof(IRequestContextFactory)); - if (obsoleteContextFactory is not null) - { - var context = await obsoleteContextFactory.CreateRequestContextAsync(this, _request, cancellationToken).ConfigureAwait(false); - return context; - } - - throw new InvalidOperationException($"No {nameof(AbstractRequestContextFactory)} or {nameof(IRequestContextFactory)} was registered with {nameof(ILspServices)}."); + var requestContextFactory = LspServices.GetRequiredService>(); + var context = await requestContextFactory.CreateRequestContextAsync(this, handler, _request, cancellationToken).ConfigureAwait(false); + return context; } -#pragma warning restore CS0618 // Type or member is obsolete /// /// Processes the queued request. Exceptions will be sent to the task completion source diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs index b24ca7e75352d..cd200a1ca0ec7 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs @@ -50,11 +50,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// more messages, and a new queue will need to be created. /// /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class RequestExecutionQueue : IRequestExecutionQueue -#else internal class RequestExecutionQueue : IRequestExecutionQueue -#endif { protected readonly ILspLogger _logger; protected readonly AbstractHandlerProvider _handlerProvider; @@ -75,19 +71,6 @@ internal class RequestExecutionQueue : IRequestExecutionQueue _cancelSource.Token; - [Obsolete($"Use constructor with {nameof(AbstractHandlerProvider)} instead.", error: false)] - public RequestExecutionQueue(AbstractLanguageServer languageServer, ILspLogger logger, IHandlerProvider handlerProvider) - { - _languageServer = languageServer; - _logger = logger; - if (handlerProvider is AbstractHandlerProvider abstractHandlerProvider) - { - _handlerProvider = abstractHandlerProvider; - } - - _handlerProvider = new WrappedHandlerProvider(handlerProvider); - } - public RequestExecutionQueue(AbstractLanguageServer languageServer, ILspLogger logger, AbstractHandlerProvider handlerProvider) { _languageServer = languageServer; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs index e9f7a2be78ed5..c0b0bd4254d43 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language) -#else internal record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language) -#endif { internal string HandlerDescription { get; } = $"{MethodName} ({Language})"; } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs index e689d852425f1..9ad2023834c93 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class RequestShutdownEventArgs : EventArgs -#else internal class RequestShutdownEventArgs : EventArgs -#endif { public string Message { get; } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs deleted file mode 100644 index 120b54dfb0c21..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// Wraps an . -/// -internal sealed class WrappedHandlerProvider : AbstractHandlerProvider -{ - private readonly IHandlerProvider _handlerProvider; - - public WrappedHandlerProvider(IHandlerProvider handlerProvider) - { - _handlerProvider = handlerProvider; - } - - public override IMethodHandler GetMethodHandler(string method, Type? requestType, Type? responseType, string language) - => _handlerProvider.GetMethodHandler(method, requestType, responseType); - - public override ImmutableArray GetRegisteredMethods() - => _handlerProvider.GetRegisteredMethods(); -} diff --git a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs index 3dc9a54510650..e59664f09dd6f 100644 --- a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs +++ b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs @@ -54,7 +54,7 @@ protected override ILspServices ConstructLspServices() protected override IRequestExecutionQueue ConstructRequestExecutionQueue() { var provider = GetLspServices().GetRequiredService>(); - return provider.CreateRequestExecutionQueue(this, _logger, HandlerProvider); + return provider.CreateRequestExecutionQueue(this, Logger, HandlerProvider); } private ImmutableDictionary>> GetBaseServices( @@ -115,7 +115,7 @@ protected override string GetLanguageForRequest(string methodName, JToken? param { if (parameters == null) { - _logger.LogInformation("No request parameters given, using default language handler"); + Logger.LogInformation("No request parameters given, using default language handler"); return LanguageServerConstants.DefaultLanguageName; } @@ -140,7 +140,7 @@ protected override string GetLanguageForRequest(string methodName, JToken? param var uri = uriToken.ToObject(_jsonSerializer); Contract.ThrowIfNull(uri, "Failed to deserialize uri property"); var language = lspWorkspaceManager.GetLanguageForUri(uri); - _logger.LogInformation($"Using {language} from request text document"); + Logger.LogInformation($"Using {language} from request text document"); return language; } @@ -154,12 +154,12 @@ protected override string GetLanguageForRequest(string methodName, JToken? param var data = dataToken.ToObject(_jsonSerializer); Contract.ThrowIfNull(data, "Failed to document resolve data object"); var language = lspWorkspaceManager.GetLanguageForUri(data.TextDocument.Uri); - _logger.LogInformation($"Using {language} from data text document"); + Logger.LogInformation($"Using {language} from data text document"); return language; } // This request is not for a textDocument and is not a resolve request. - _logger.LogInformation("Request did not contain a textDocument, using default language handler"); + Logger.LogInformation("Request did not contain a textDocument, using default language handler"); return LanguageServerConstants.DefaultLanguageName; static bool ShouldUseDefaultLanguage(string methodName) diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 5d2ff4a4c435a..d739438f4952f 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -135,12 +135,6 @@ true BindingRedirect - - Microsoft.CommonLanguageServerProtocolFramework - BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup - true - BindingRedirect - LiveShareLanguageServices BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup From e83a143d4d8f9f5e022b930bba537f4beaa2c094 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 19:39:35 -0700 Subject: [PATCH 0663/1047] Store as checksum --- .../Portable/Serialization/SerializableSourceText.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 0a7bcf15f227c..ad9ded5d6bd10 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -45,12 +45,6 @@ internal sealed class SerializableSourceText /// private readonly SourceText? _text; - /// - /// The hash that would be produced by calling on . Can be passed in when already known to avoid unnecessary computation costs. - /// - private readonly ImmutableArray _contentHash; - /// /// Weak reference to a SourceText computed from . Useful so that if multiple requests /// come in for the source text, the same one can be returned as long as something is holding it alive. @@ -60,7 +54,7 @@ internal sealed class SerializableSourceText /// /// Checksum of the contents (see ) of the text. /// - public Checksum ContentChecksum => Checksum.Create(_contentHash); + public readonly Checksum ContentChecksum; public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) : this(storage, text: null, contentHash) @@ -78,7 +72,7 @@ private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, _storage = storage; _text = text; - _contentHash = contentHash; + ContentChecksum = Checksum.Create(contentHash); #if DEBUG var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; From 2044967360e677f1ba2285df510342275b81c0a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 23:02:06 -0700 Subject: [PATCH 0664/1047] skip test --- .../IntegrationTest/New.IntegrationTests/InfrastructureTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs index c11174300d08a..f520004406aed 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs @@ -22,7 +22,7 @@ public InfrastructureTests() protected override string LanguageName => LanguageNames.CSharp; - [IdeFact] + [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73099")] public async Task CanCloseSaveDialog() { await SetUpEditorAsync( From c58aa81fae15c791b4629bf9d95b18774ed364d9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 23:02:06 -0700 Subject: [PATCH 0665/1047] skip test --- .../IntegrationTest/New.IntegrationTests/InfrastructureTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs index c11174300d08a..f520004406aed 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs @@ -22,7 +22,7 @@ public InfrastructureTests() protected override string LanguageName => LanguageNames.CSharp; - [IdeFact] + [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73099")] public async Task CanCloseSaveDialog() { await SetUpEditorAsync( From 27a91c523036658cfbd8f9408181bd70910b2a71 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 00:42:15 -0700 Subject: [PATCH 0666/1047] Docs --- .../Core/Portable/Workspace/Solution/TextLoader.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index 9b4ab7c3e2d86..cf625034f68d8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -33,13 +33,15 @@ public abstract class TextLoader /// /// if the document that holds onto this loader should do so with a strong reference, versus - /// a reference that will take the contents of this loader and store them in a recoverable form. This should be - /// used when the underlying data is already stored in a recoverable form somewhere else and it would be wasteful to - /// store another copy. For example, a document that is backed by memory-mapped contents in another process does not - /// need to dump it's content to another memory-mapped file in the process it lives in. It can always recover the - /// text from the original process. + /// a reference that will take the contents of this loader and store them in a recoverable form (e.g. a memory + /// mapped file within the same process). This should be used when the underlying data is already stored + /// in a recoverable form somewhere else and it would be wasteful to store another copy. For example, a document + /// that is backed by memory-mapped contents in another process does not need to dump it's content to + /// another memory-mapped file in the process it lives in. It can always recover the text from the original + /// process. /// - internal virtual bool AlwaysHoldStrongly => false; + internal virtual bool AlwaysHoldStrongly + => false; /// /// True if reloads from its original binary representation (e.g. file on disk). From fcbdd25748028f478113961031a6741127c139ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 00:46:20 -0700 Subject: [PATCH 0667/1047] Docs --- .../Core/Portable/Workspace/Solution/TextDocumentState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 1ec64287e0972..bc4f27fc513c6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -175,6 +175,11 @@ private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, Pre var service = solutionServices.GetRequiredService(); var options = service.Options; + // See if the client, the loader itself, or the options want us to hold onto this loader strongly. If so, we + // wrap it directly in a LoadableTextAndVersionSource that will keep it alive, deferring to it to get the final + // source text. If none of the above hold, we'll create a RecoverableTextAndVersion. This will use the loaded + // the first time to get the text contents, but will then dump those contents to a memory-mapped-file afterwards + // so that it can be kept out of main memory, while still allowing us to preserve snapshot semantics. return mode == PreservationMode.PreserveIdentity || loader.AlwaysHoldStrongly || options.DisableRecoverableText ? new LoadableTextAndVersionSource(loader, cacheResult: true) : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); From 4f4f9bd032df38c7e0ef5c7300758c27c194ef13 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 00:49:05 -0700 Subject: [PATCH 0668/1047] Docs --- .../Host/RemoteWorkspace.SolutionCreator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 9bf1cccabf9c4..9523c67fac044 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -619,12 +619,11 @@ private async Task UpdateDocumentAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var loader = serializableSourceText.ToTextLoader(document.FilePath); - // We strongly want to use identity-preservation with these loaders. By doing that we'll always use - // this loader as the 'truth' of how to get teh source-text for a particular document. When the - // text is first needed it will be pulled from the memory mapped files on the host side. Then, if - // that text is ever dropped, we'll still be pointing at this same loader. That will ensure that we - // still read the data from the host side, without doing something silly (like dumping it to our own - // memory mapped data on the OOP side). + // The mode here doesn't actually matter. The text loaded created by ToTextLoader will already tell + // the solution to use it as the source of truth for the document and to not then dump the contents + // of it to a memory-mapped-file within this process (no point when it's already that way in the + // shared memory-mapped-file we are pointing to in the host process). But 'PreserveIdentity' + // matches that as well so we use that mode here for clarity. var mode = PreservationMode.PreserveIdentity; document = document.Kind switch From a45748cc9c5faf264a1f23e72138a0e5ceddd87d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 01:02:49 -0700 Subject: [PATCH 0669/1047] revert --- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 9523c67fac044..5a44050959030 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -618,13 +618,7 @@ private async Task UpdateDocumentAsync( var serializableSourceText = await _assetProvider.GetAssetAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var loader = serializableSourceText.ToTextLoader(document.FilePath); - - // The mode here doesn't actually matter. The text loaded created by ToTextLoader will already tell - // the solution to use it as the source of truth for the document and to not then dump the contents - // of it to a memory-mapped-file within this process (no point when it's already that way in the - // shared memory-mapped-file we are pointing to in the host process). But 'PreserveIdentity' - // matches that as well so we use that mode here for clarity. - var mode = PreservationMode.PreserveIdentity; + var mode = PreservationMode.PreserveValue; document = document.Kind switch { From 8ac8c86ce8bae98c1cfc8da6b873f28027147614 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 01:07:12 -0700 Subject: [PATCH 0670/1047] Fix --- .../Workspace/Solution/TextDocumentState.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index bc4f27fc513c6..53c6008610ccc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -175,14 +173,22 @@ private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, Pre var service = solutionServices.GetRequiredService(); var options = service.Options; - // See if the client, the loader itself, or the options want us to hold onto this loader strongly. If so, we - // wrap it directly in a LoadableTextAndVersionSource that will keep it alive, deferring to it to get the final - // source text. If none of the above hold, we'll create a RecoverableTextAndVersion. This will use the loaded - // the first time to get the text contents, but will then dump those contents to a memory-mapped-file afterwards - // so that it can be kept out of main memory, while still allowing us to preserve snapshot semantics. - return mode == PreservationMode.PreserveIdentity || loader.AlwaysHoldStrongly || options.DisableRecoverableText - ? new LoadableTextAndVersionSource(loader, cacheResult: true) - : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); + // If the caller is explicitly stating that identity must be preserved, then we created a source that will load + // from the loader the first time, but then cache that result so that hte same result is *always* returned. + if (mode == PreservationMode.PreserveIdentity || options.DisableRecoverableText) + return new LoadableTextAndVersionSource(loader, cacheResult: true); + + // If the loader asks us to always hold onto it strongly, then we do not want to create a recoverable text + // source here. Instead, we'll go back to the loader each time to get the text. This is useful for when the + // loader knows it can always reconstitute the snapshot exactly as it was before. For example, if the loader + // points at the contents of a memory mapped file in another process. + if (loader.AlwaysHoldStrongly) + return new LoadableTextAndVersionSource(loader, cacheResult: false); + + // Otherwise, we just want to hold onto this loader by value. So we create a loader that will load the + // contents, but not hold onto them strongly, and we wrap it in a recoverable-text that will then take those + // contents and dump it into a memory-mapped-file in this process so that snapshot semantics can be preserved. + return new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); } protected virtual TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) From 7c55f24208ad97d6ee949472b91386a851bfecfc Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Fri, 19 Apr 2024 07:01:12 -0700 Subject: [PATCH 0671/1047] Change IReferenceFinder.DetermineDocumentsToSearchAsync to not return an ImmutableArray (#73093) Change IReferenceFinder.DetermineDocumentsToSearchAsync to not return an ImmutableArray Rather, this method now encourages items that wish to be part of the result to invoke a callback with that result. This prevents quite a few intermediary array allocations during find references invocations. --- .../DelegateInvokeMethodReferenceFinder.cs | 10 ++- .../FindReferencesSearchEngine.cs | 16 +++-- .../AbstractMemberScopedReferenceFinder.cs | 14 ++-- .../Finders/AbstractReferenceFinder.cs | 71 +++++++++++-------- ...tructorInitializerSymbolReferenceFinder.cs | 9 +-- .../ConstructorSymbolReferenceFinder.cs | 48 ++++++------- .../DestructorSymbolReferenceFinder.cs | 8 ++- .../Finders/EventSymbolReferenceFinder.cs | 10 +-- ...ExplicitConversionSymbolReferenceFinder.cs | 17 ++--- .../ExplicitInterfaceMethodReferenceFinder.cs | 8 ++- .../Finders/FieldSymbolReferenceFinder.cs | 11 +-- .../Finders/IReferenceFinder.cs | 4 +- ...ethodTypeParameterSymbolReferenceFinder.cs | 7 +- .../Finders/NamedTypeSymbolReferenceFinder.cs | 39 +++++----- .../Finders/NamespaceSymbolReferenceFinder.cs | 23 +++--- .../Finders/OperatorSymbolReferenceFinder.cs | 19 ++--- .../Finders/OrdinaryMethodReferenceFinder.cs | 45 ++++++------ .../Finders/ParameterSymbolReferenceFinder.cs | 6 +- .../PropertyAccessorSymbolReferenceFinder.cs | 16 ++--- .../Finders/PropertySymbolReferenceFinder.cs | 36 +++++----- .../TypeParameterSymbolReferenceFinder.cs | 7 +- 21 files changed, 232 insertions(+), 192 deletions(-) diff --git a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index 023fb637ee227..8b1bd852308cb 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -59,15 +60,20 @@ protected override async ValueTask> DetermineCascadedSym return result.ToImmutableAndClear(); } - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return Task.FromResult(project.Documents.ToImmutableArray()); + foreach (var document in project.Documents) + processResult(document, processResultData); + + return Task.CompletedTask; } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 8d788e83130bb..25f61c8deda53 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -190,6 +190,9 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); try { + // scratch hashset to place results in. Populated/inspected/cleared in inner loop. + using var _3 = PooledHashSet.GetInstance(out var foundDocuments); + await AddGlobalAliasesAsync(project, allSymbols, symbolToGlobalAliases, cancellationToken).ConfigureAwait(false); foreach (var symbol in allSymbols) @@ -198,18 +201,23 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray foreach (var finder in _finders) { - var documents = await finder.DetermineDocumentsToSearchAsync( - symbol, globalAliases, project, _documents, _options, cancellationToken).ConfigureAwait(false); + await finder.DetermineDocumentsToSearchAsync( + symbol, globalAliases, project, _documents, + static (doc, documents) => documents.Add(doc), + foundDocuments, + _options, cancellationToken).ConfigureAwait(false); - foreach (var document in documents) + foreach (var document in foundDocuments) { var docSymbols = GetSymbolSet(documentToSymbols, document); docSymbols.Add(symbol); } + + foundDocuments.Clear(); } } - using var _3 = ArrayBuilder.GetInstance(out var tasks); + using var _4 = ArrayBuilder.GetInstance(out var tasks); foreach (var (document, docSymbols) in documentToSymbols) { tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 224ab915fc901..5e70cfac3b188 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -23,26 +24,29 @@ protected abstract bool TokensMatch( protected sealed override bool CanFind(TSymbol symbol) => true; - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( TSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var location = symbol.Locations.FirstOrDefault(); if (location == null || !location.IsInSource) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; var document = project.GetDocument(location.SourceTree); if (document == null) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; if (documents != null && !documents.Contains(document)) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; - return Task.FromResult(ImmutableArray.Create(document)); + processResult(document, processResultData); + return Task.CompletedTask; } protected sealed override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 1ac57d9d93596..c2a586f94d1e3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -24,14 +24,17 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder public const string ContainingTypeInfoPropertyName = "ContainingTypeInfo"; public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo"; + protected static readonly Action> StandardHashSetAddCallback = + static (doc, set) => set.Add(doc); + public abstract Task> DetermineGlobalAliasesAsync( ISymbol symbol, Project project, CancellationToken cancellationToken); public abstract ValueTask> DetermineCascadedSymbolsAsync( ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); + public abstract Task DetermineDocumentsToSearchAsync( + ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); public abstract ValueTask> FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); @@ -81,11 +84,13 @@ protected static bool TryGetNameWithoutAttributeSuffix( return name.TryGetWithoutAttributeSuffix(syntaxFacts.IsCaseSensitive, out result); } - protected static async Task> FindDocumentsAsync( + protected static async Task FindDocumentsAsync( Project project, IImmutableSet? scope, Func> predicateAsync, T value, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // special case for highlight references @@ -93,31 +98,30 @@ protected static async Task> FindDocumentsAsync( { var document = scope.First(); if (document.Project == project) - return scope.ToImmutableArray(); + processResult(document, processResultData); - return []; + return; } - using var _ = ArrayBuilder.GetInstance(out var documents); foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) { if (scope != null && !scope.Contains(document)) continue; if (await predicateAsync(document, value, cancellationToken).ConfigureAwait(false)) - documents.Add(document); + processResult(document, processResultData); } - - return documents.ToImmutableAndClear(); } /// /// Finds all the documents in the provided project that contain the requested string /// values /// - protected static Task> FindDocumentsAsync( + protected static Task FindDocumentsAsync( Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, CancellationToken cancellationToken, params string[] values) { @@ -130,30 +134,32 @@ protected static Task> FindDocumentsAsync( } return true; - }, values, cancellationToken); + }, values, processResult, processResultData, cancellationToken); } /// /// Finds all the documents in the provided project that contain a global attribute in them. /// - protected static Task> FindDocumentsWithGlobalSuppressMessageAttributeAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + protected static Task FindDocumentsWithGlobalSuppressMessageAttributeAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, cancellationToken); + project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, processResult, processResultData, cancellationToken); } - protected static Task> FindDocumentsAsync( + protected static Task FindDocumentsAsync( Project project, IImmutableSet? documents, PredefinedType predefinedType, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (predefinedType == PredefinedType.None) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; return FindDocumentsWithPredicateAsync( - project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, cancellationToken); + project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, processResult, processResultData, cancellationToken); } protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token) @@ -327,35 +333,41 @@ private static async Task> FindReferencesThroughL return allAliasReferences.ToImmutableAndClear(); } - protected static Task> FindDocumentsWithPredicateAsync( + protected static Task FindDocumentsWithPredicateAsync( Project project, IImmutableSet? documents, Func predicate, T value, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindDocumentsAsync(project, documents, static async (d, t, c) => { var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false); return t.predicate(info, t.value); - }, (predicate, value), cancellationToken); + }, (predicate, value), processResult, processResultData, cancellationToken); } - protected static Task> FindDocumentsWithPredicateAsync( + protected static Task FindDocumentsWithPredicateAsync( Project project, IImmutableSet? documents, Func predicate, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( project, documents, static (info, predicate) => predicate(info), predicate, + processResult, + processResultData, cancellationToken); } - protected static Task> FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, cancellationToken); + protected static Task FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, processResult, processResultData, cancellationToken); /// /// If the `node` implicitly matches the `symbol`, then it will be added to `locations`. @@ -824,8 +836,9 @@ internal abstract partial class AbstractReferenceFinder : AbstractRefer { protected abstract bool CanFind(TSymbol symbol); - protected abstract Task> DetermineDocumentsToSearchAsync( + protected abstract Task DetermineDocumentsToSearchAsync( TSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); protected abstract ValueTask> FindReferencesInDocumentAsync( @@ -845,13 +858,15 @@ public sealed override Task> DetermineGlobalAliasesAsync( : SpecializedTasks.EmptyImmutableArray(); } - public sealed override Task> DetermineDocumentsToSearchAsync( + public sealed override Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, - IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) + IImmutableSet? documents, Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, options, cancellationToken) - : SpecializedTasks.EmptyImmutableArray(); + if (symbol is TSymbol typedSymbol && CanFind(typedSymbol)) + return DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, processResult, processResultData, options, cancellationToken); + + return Task.CompletedTask; } public sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 88dc9fad96af2..c79b50715238d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -19,11 +18,13 @@ internal sealed class ConstructorInitializerSymbolReferenceFinder : AbstractRefe protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Constructor; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -47,7 +48,7 @@ protected override Task> DetermineDocumentsToSearchAsyn } return false; - }, symbol.ContainingType.Name, cancellationToken); + }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } protected sealed override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index f4e3e0828a5d5..8fea80d2ffb77 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -32,62 +31,59 @@ protected override Task> DetermineGlobalAliasesAsync(IMet return GetAllMatchingGlobalAliasNamesAsync(project, containingType.Name, containingType.Arity, cancellationToken); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var containingType = symbol.ContainingType; var typeName = symbol.ContainingType.Name; - using var _ = ArrayBuilder.GetInstance(out var result); - await AddDocumentsAsync( - project, documents, typeName, result, cancellationToken).ConfigureAwait(false); + project, documents, typeName, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var globalAlias in globalAliases) { await AddDocumentsAsync( - project, documents, globalAlias, result, cancellationToken).ConfigureAwait(false); + project, documents, globalAlias, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - result.AddRange(await FindDocumentsAsync( - project, documents, containingType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, containingType.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false); - result.AddRange(symbol.MethodKind == MethodKind.Constructor - ? await FindDocumentsWithImplicitObjectCreationExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + if (symbol.MethodKind == MethodKind.Constructor) + { + await FindDocumentsWithImplicitObjectCreationExpressionAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } } - private static Task> FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, cancellationToken); + private static Task FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, processResult, processResultData, cancellationToken); - private static async Task AddDocumentsAsync( + private static async Task AddDocumentsAsync( Project project, IImmutableSet? documents, string typeName, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, typeName).ConfigureAwait(false); - - var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService(), out var simpleName) - ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) - : []; + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, typeName).ConfigureAwait(false); - result.AddRange(documentsWithName); - result.AddRange(documentsWithAttribute); + if (TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService(), out var simpleName)) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false); } private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxFactsService syntaxFacts, SyntaxToken token) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs index 610db4da1aeac..245f390365b46 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -15,15 +15,17 @@ internal sealed class DestructorSymbolReferenceFinder : AbstractReferenceFinder< protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Destructor; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 756bffd4366c4..76a169a867822 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -33,17 +34,18 @@ protected sealed override ValueTask> DetermineCascadedSy return new(backingFields.Concat(associatedNamedTypes)); } - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IEventSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index 7059fefdf72ab..b4e00a0482da6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -23,11 +24,13 @@ protected override bool CanFind(IMethodSymbol symbol) private static INamedTypeSymbol? GetUnderlyingNamedType(ITypeSymbol symbol) => UnderlyingNamedTypeVisitor.Instance.Visit(symbol); - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -43,20 +46,18 @@ protected sealed override async Task> DetermineDocument var underlyingNamedType = GetUnderlyingNamedType(symbol.ReturnType); Contract.ThrowIfNull(underlyingNamedType); - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); - var documentsWithType = await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var result); + using var _ = PooledHashSet.GetInstance(out var result); + await FindDocumentsAsync(project, documents, StandardHashSetAddCallback, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardHashSetAddCallback, result, cancellationToken).ConfigureAwait(false); // Ignore any documents that don't also have an explicit cast in them. - foreach (var document in documentsWithName.Concat(documentsWithType).Distinct()) + foreach (var document in result) { var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); if (index.ContainsConversion) - result.Add(document); + processResult(document, processResultData); } - - return result.ToImmutableAndClear(); } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index 51256d807e4b0..b048e24c1a03f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -15,16 +15,18 @@ internal sealed class ExplicitInterfaceMethodReferenceFinder : AbstractReference protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.ExplicitInterfaceImplementation; - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // An explicit method can't be referenced anywhere. - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 43c3c03d7ccdc..d3657581f7cfd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -26,17 +26,18 @@ protected override ValueTask> DetermineCascadedSymbolsAs : new(ImmutableArray.Empty); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IFieldSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 395fcb5402ca3..8e4738b2a4728 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -49,9 +50,10 @@ ValueTask> DetermineCascadedSymbolsAsync( /// /// Implementations of this method must be thread-safe. /// - Task> DetermineDocumentsToSearchAsync( + Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); /// diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs index 78d29392e7082..55150a8b42bf7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -36,11 +37,13 @@ protected override ValueTask> DetermineCascadedSymbolsAs return new([]); } - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -55,7 +58,7 @@ protected sealed override Task> DetermineDocumentsToSea // Also, we only look for files that have the name of the owning type. This helps filter // down the set considerably. Contract.ThrowIfNull(symbol.DeclaringMethod); - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, GetMemberNameWithoutInterfaceName(symbol.DeclaringMethod.Name), symbol.DeclaringMethod.ContainingType.Name); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 74537c547319e..ff8f1084f9b13 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -10,7 +11,6 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -49,54 +49,49 @@ private static void Add(ArrayBuilder result, ImmutableArray()); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( INamedTypeSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - - await AddDocumentsToSearchAsync(symbol.Name, project, documents, result, cancellationToken).ConfigureAwait(false); + await AddDocumentsToSearchAsync(symbol.Name, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var alias in globalAliases) - await AddDocumentsToSearchAsync(alias, project, documents, result, cancellationToken).ConfigureAwait(false); + await AddDocumentsToSearchAsync(alias, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - result.AddRange(await FindDocumentsAsync( - project, documents, symbol.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, symbol.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } /// /// Looks for documents likely containing in them. That name will either be the actual /// name of the named type we're looking for, or it might be a global alias to it. /// - private static async Task AddDocumentsToSearchAsync( + private static async Task AddDocumentsToSearchAsync( string throughName, Project project, IImmutableSet? documents, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var syntaxFacts = project.Services.GetRequiredService(); - var documentsWithName = await FindDocumentsAsync( - project, documents, cancellationToken, throughName).ConfigureAwait(false); - - var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName) - ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) - : []; + await FindDocumentsAsync( + project, documents, processResult, processResultData, cancellationToken, throughName).ConfigureAwait(false); - result.AddRange(documentsWithName); - result.AddRange(documentsWithAttribute); + if (TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName)) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index dc0f2a36f79a3..bb03240ba2ce4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -22,33 +23,31 @@ protected override Task> DetermineGlobalAliasesAsync(INam return GetAllMatchingGlobalAliasNamesAsync(project, symbol.Name, arity: 0, cancellationToken); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( INamespaceSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - - result.AddRange(!symbol.IsGlobalNamespace - ? await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false) - : await FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsGlobalKeyword, cancellationToken).ConfigureAwait(false)); + if (!symbol.IsGlobalNamespace) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + else + await FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsGlobalKeyword, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var globalAlias in globalAliases) { - result.AddRange(await FindDocumentsAsync( - project, documents, cancellationToken, globalAlias).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, processResult, processResultData, cancellationToken, globalAlias).ConfigureAwait(false); } } - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - result.AddRange(documentsWithGlobalAttributes); - - return result.ToImmutableAndClear(); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index a4135e8d5f065..bd1d4210e5476 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -2,13 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -17,31 +17,34 @@ internal sealed class OperatorSymbolReferenceFinder : AbstractMethodOrPropertyOr protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind is MethodKind.UserDefinedOperator or MethodKind.BuiltinOperator; - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var op = symbol.GetPredefinedOperator(); - var documentsWithOp = await FindDocumentsAsync(project, documents, op, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithOp.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, op, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static Task> FindDocumentsAsync( + private static Task FindDocumentsAsync( Project project, IImmutableSet? documents, PredefinedOperator op, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (op == PredefinedOperator.None) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; return FindDocumentsWithPredicateAsync( - project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, cancellationToken); + project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } protected sealed override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 91ab4963aa113..7ace214c57e6e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -42,11 +43,13 @@ private static ImmutableArray GetOtherPartsOfPartial(IMethodSymbol symb return []; } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol methodSymbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -65,38 +68,32 @@ protected override async Task> DetermineDocumentsToSear // searches for these, then we should find usages of 'lock(goo)' or 'synclock(goo)' // since they implicitly call those methods. - var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, methodSymbol.Name).ConfigureAwait(false); - var forEachDocuments = IsForEachMethod(methodSymbol) - ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, methodSymbol.Name).ConfigureAwait(false); - var deconstructDocuments = IsDeconstructMethod(methodSymbol) - ? await FindDocumentsWithDeconstructionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachMethod(methodSymbol)) + await FindDocumentsWithForEachStatementsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var awaitExpressionDocuments = IsGetAwaiterMethod(methodSymbol) - ? await FindDocumentsWithAwaitExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsDeconstructMethod(methodSymbol)) + await FindDocumentsWithDeconstructionAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false); + if (IsGetAwaiterMethod(methodSymbol)) + await FindDocumentsWithAwaitExpressionAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithCollectionInitializers = IsAddMethod(methodSymbol) - ? await FindDocumentsWithCollectionInitializersAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return ordinaryDocuments.Concat( - forEachDocuments, deconstructDocuments, awaitExpressionDocuments, documentsWithGlobalAttributes, documentsWithCollectionInitializers); + if (IsAddMethod(methodSymbol)) + await FindDocumentsWithCollectionInitializersAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static Task> FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsDeconstruction, cancellationToken); + private static Task FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsDeconstruction, processResult, processResultData, cancellationToken); - private static Task> FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsAwait, cancellationToken); + private static Task FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsAwait, processResult, processResultData, cancellationToken); - private static Task> FindDocumentsWithCollectionInitializersAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsCollectionInitializer, cancellationToken); + private static Task FindDocumentsWithCollectionInitializersAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsCollectionInitializer, processResult, processResultData, cancellationToken); private static bool IsForEachMethod(IMethodSymbol methodSymbol) => methodSymbol.Name is WellKnownMemberNames.GetEnumeratorMethodName or diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index c15af3ce4355e..d7231cad5721a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -20,11 +20,13 @@ internal sealed class ParameterSymbolReferenceFinder : AbstractReferenceFinder true; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -33,7 +35,7 @@ protected override Task> DetermineDocumentsToSearchAsyn // elsewhere as "paramName:" or "paramName:=". We can narrow the search by // filtering down to matches of that form. For now we just return any document // that references something with this name. - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name); + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name); } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 383c82ad44096..a15f25ab2979c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -30,34 +29,35 @@ protected override ValueTask> DetermineCascadedSymbolsAs : new(ImmutableArray.Create(symbol.AssociatedSymbol)); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // First, find any documents with the full name of the accessor (i.e. get_Goo). // This will find explicit calls to the method (which can happen when C# references // a VB parameterized property). - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); - var propertyDocuments = ImmutableArray.Empty; if (symbol.AssociatedSymbol is IPropertySymbol property && options.AssociatePropertyReferencesWithSpecificAccessor) { // we want to associate normal property references with the specific accessor being // referenced. So we also need to include documents with our property's name. Just // defer to the Property finder to find these docs and combine them with the result. - propertyDocuments = await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( + await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( property, globalAliases, project, documents, + processResult, processResultData, options with { AssociatePropertyReferencesWithSpecificAccessor = false }, cancellationToken).ConfigureAwait(false); } - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(propertyDocuments, documentsWithGlobalAttributes); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 6d2acf8ebeb77..0f228294634e1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -91,30 +91,28 @@ private static void CascadeToPrimaryConstructorParameters(IPropertySymbol proper } } - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IPropertySymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); - var forEachDocuments = IsForEachProperty(symbol) - ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachProperty(symbol)) + await FindDocumentsWithForEachStatementsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var elementAccessDocument = symbol.IsIndexer - ? await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var indexerMemberCrefDocument = symbol.IsIndexer - ? await FindDocumentWithIndexerMemberCrefAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindDocumentWithIndexerMemberCrefAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return ordinaryDocuments.Concat(forEachDocuments, elementAccessDocument, indexerMemberCrefDocument, documentsWithGlobalAttributes); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static bool IsForEachProperty(IPropertySymbol symbol) @@ -155,18 +153,18 @@ protected sealed override async ValueTask> FindRe return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences); } - private static Task> FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, cancellationToken); + project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, processResult, processResultData, cancellationToken); } - private static Task> FindDocumentWithIndexerMemberCrefAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + private static Task FindDocumentWithIndexerMemberCrefAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsIndexerMemberCref, cancellationToken); + project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } private static async Task> FindIndexerReferencesAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs index 32587f95c601e..6b1ffc51734be 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -14,11 +15,13 @@ internal sealed class TypeParameterSymbolReferenceFinder : AbstractTypeParameter protected override bool CanFind(ITypeParameterSymbol symbol) => symbol.TypeParameterKind != TypeParameterKind.Method; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -29,6 +32,6 @@ protected override Task> DetermineDocumentsToSearchAsyn // parameter has a different name in different parts that we won't find it. However, // this only happens in error situations. It is not legal in C# to use a different // name for a type parameter in different parts. - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, symbol.ContainingType.Name); + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, symbol.ContainingType.Name); } } From 0cf9b067a09775494aa03c7797ba84743dbc1ca8 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 19 Apr 2024 08:59:53 -0700 Subject: [PATCH 0672/1047] Add partial properties feature status (#73091) --- docs/Language Feature Status.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 3ba66cf3fe790..6a36d6df37b25 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,6 +10,7 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | IDE Buddy | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | --------- | +| [Partial properties](https://github.com/dotnet/csharplang/issues/6420) | [partial-properties](https://github.com/dotnet/roslyn/tree/features/partial-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/73090) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | TBD | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | Ref/unsafe in iterators/async | [RefInAsync](https://github.com/dotnet/roslyn/tree/features/RefInAsync) | [In Progress](https://github.com/dotnet/roslyn/issues/72662) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | [ToddGrun](https://github.com/ToddGrun) | | | [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [ToddGrun](https://github.com/ToddGrun) | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | | [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | From 3259fe9027b4201e4a42e1bdaa10194840f78706 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 19 Apr 2024 11:05:44 -0700 Subject: [PATCH 0673/1047] Keep entry in publishdata for 17.10 --- eng/config/PublishData.json | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index ee01121b84fd6..6c568c4301a1c 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -89,6 +89,7 @@ "Microsoft.VisualStudio.LanguageServices.LiveShare": "vs-impl", "Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient": "vs-impl", "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl", + "Microsoft.CommonLanguageServerProtocol.Framework.Binary": "vs-impl" } }, "comment-about-servicing-branches": "For a list of VS versions under servicing, see https://docs.microsoft.com/en-us/visualstudio/releases/2019/servicing#support-options-for-enterprise-and-professional-customers", From b543bdf26a95e464303ccf4f1ac5b0bd16cbacf0 Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Fri, 19 Apr 2024 11:20:09 -0700 Subject: [PATCH 0674/1047] Include file name in Contract failures (#73092) This adds the file names to the messages generated by contract failures. This information would've made it _significantly_ easier to debug a recent DDRIT failure. --- ...tionCompilationState.CompilationTracker.cs | 2 +- .../Compiler/Core/Utilities/Contract.cs | 48 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index e0a5cd666f168..d38606df450db 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -1019,7 +1019,7 @@ private static void ValidateCompilationTreesMatchesProjectState(Compilation comp } /// - /// This is just the same as but throws a custom exception type to make this easier to find in telemetry since the exception type + /// This is just the same as but throws a custom exception type to make this easier to find in telemetry since the exception type /// is easily seen in telemetry. /// private static void ThrowExceptionIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs index 331ca8a3c5653..db14883bd1652 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.CompilerServices; namespace Roslyn.Utilities; @@ -22,11 +23,11 @@ internal static partial class Contract /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0) where T : class? + public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) where T : class? { if (value is null) { - Fail("Unexpected null", lineNumber); + Fail("Unexpected null", lineNumber, filePath); } } @@ -35,11 +36,11 @@ public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int line /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lineNumber = 0) where T : struct + public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) where T : struct { if (value is null) { - Fail("Unexpected null", lineNumber); + Fail("Unexpected null", lineNumber, filePath); } } @@ -48,11 +49,11 @@ public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lin /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (value is null) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -61,11 +62,11 @@ public static void ThrowIfNull([NotNull] T value, string message, [CallerLine /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerArgument("value")] ThrowIfNullInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerArgument("value")] ThrowIfNullInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (value is null) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } @@ -74,11 +75,11 @@ public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerA /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail("Unexpected false", lineNumber); + Fail("Unexpected false", lineNumber, filePath); } } @@ -87,11 +88,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -100,11 +101,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfFalseInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfFalseInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } @@ -113,11 +114,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail("Unexpected true", lineNumber); + Fail("Unexpected true", lineNumber, filePath); } } @@ -126,11 +127,11 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -139,17 +140,20 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfTrueInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfTrueInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } [DebuggerHidden] [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0) - => throw new InvalidOperationException($"{message} - line {lineNumber}"); + public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) + { + var fileName = filePath is null ? null : Path.GetFileName(filePath); + throw new InvalidOperationException($"{message} - file {fileName} line {lineNumber}"); + } } From 6bec9a66884f9910e27a51e91de43df9556ca84e Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Fri, 19 Apr 2024 11:38:38 -0700 Subject: [PATCH 0675/1047] One diagnostic source provider per source name --- ...bstractDocumentDiagnosticSourceProvider.cs | 41 +++++ ...stractWorkspaceDiagnosticSourceProvider.cs | 149 +----------------- .../DiagnosticSourceManager.cs | 74 +++------ .../DocumentDiagnosticSource.cs | 1 - .../IDiagnosticSourceProvider.cs | 9 +- .../DocumentDiagnosticSourceProvider.cs | 131 --------------- ...EditAndContinueDiagnosticSourceProvider.cs | 30 ++++ ...ntaxAndSemanticDiagnosticSourceProvider.cs | 72 +++++++++ .../DocumentTaskDiagnosticSourceProvider.cs | 37 +++++ .../PublicDocumentDiagnosticSourceProvider.cs | 22 ++- ...ocumentNonLocalDiagnosticSourceProvider.cs | 43 +++++ .../PublicDocumentPullDiagnosticsHandler.cs | 8 +- ...PublicWorkspaceDiagnosticSourceProvider.cs | 5 +- .../PublicWorkspacePullDiagnosticsHandler.cs | 2 +- .../WorkspaceDiagnosticSourceProvider.cs | 23 --- ...mentsAndProjectDiagnosticSourceProvider.cs | 118 ++++++++++++++ ...EditAndContinueDiagnosticSourceProvider.cs | 32 ++++ .../WorkspaceTaskDiagnosticSourceProvider.cs | 50 ++++++ ...stractHotReloadDiagnosticSourceProvider.cs | 6 +- ...cumentHotReloadDiagnosticSourceProvider.cs | 4 +- ...kspaceHotReloadDiagnosticSourceProvider.cs | 4 +- 21 files changed, 480 insertions(+), 381 deletions(-) create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..f5f014ddbde07 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => name; + + public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); + + protected static TextDocument? GetOpenDocument(RequestContext context) + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return null; + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return null; + } + + return textDocument; + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 1eedb04f10b46..473aecf7010d5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -2,80 +2,33 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.TaskList; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal abstract class AbstractWorkspaceDiagnosticSourceProvider( - IDiagnosticAnalyzerService diagnosticAnalyzerService, - IGlobalOptionService globalOptions, - ImmutableArray sourceNames) - : IDiagnosticSourceProvider +internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider { public bool IsDocument => false; - public ImmutableArray SourceNames => sourceNames; + public string Name => name; - public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); + + protected static bool ShouldIgnoreContext(RequestContext context) { // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for // document-diagnostics instead. - if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) - return []; - - if (sourceName == PullDiagnosticCategories.Task) - return GetTaskListDiagnosticSources(context, globalOptions); - - if (sourceName == PullDiagnosticCategories.EditAndContinue) - return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); - - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - if (sourceName == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, globalOptions, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); - - // if it's a category we don't recognize, return nothing. - return []; - } - - private static ImmutableArray GetTaskListDiagnosticSources( - RequestContext context, IGlobalOptionService globalOptions) - { - Contract.ThrowIfNull(context.Solution); - - // Only compute task list items for closed files if the option is on for it. - var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; - if (!taskListEnabled) - return []; - - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) - { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } - } - - return result.ToImmutableAndClear(); + return context.ServerKind == WellKnownLspServerKinds.RazorLspServer; } - private static IEnumerable GetProjectsInPriorityOrder( + protected static IEnumerable GetProjectsInPriorityOrder( Solution solution, ImmutableArray supportedLanguages) { return GetProjectsInPriorityOrderWorker(solution) @@ -103,7 +56,7 @@ private static IEnumerable GetProjectsInPriorityOrder( } } - private static bool ShouldSkipDocument(RequestContext context, TextDocument document) + protected static bool ShouldSkipDocument(RequestContext context, TextDocument document) { // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). // Each handler treats those as separate worlds that they are responsible for. @@ -117,91 +70,5 @@ private static bool ShouldSkipDocument(RequestContext context, TextDocument docu // for any razor file they are interested in. return document.IsRazorDocument(); } - - /// - /// There are three potential sources for reporting workspace diagnostics: - /// - /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest - /// project snapshot and return up-to-date diagnostics computed from this analysis. - /// - /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly - /// triggered code analysis execution on either the current or a prior project snapshot, we return - /// diagnostics from this execution. These diagnostics may be stale with respect to the current - /// project snapshot, but they match user's intent of not enabling continuous background analysis - /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on - /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. - /// - /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. - /// - /// If full solution analysis is disabled AND code analysis was never executed for the given project, - /// we have no workspace diagnostics to report and bail out. - /// - public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(context.Solution); - - using var _ = ArrayBuilder.GetInstance(out var result); - - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); - - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); - - return result.ToImmutableAndClear(); - - async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; - - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); - - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } - - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); - - return; - - void AddDocumentSources(IEnumerable documents) - { - foreach (var document in documents) - { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } - } - } - - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; - } - } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 664bd043fa3af..7acc623d0e11d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -7,84 +7,52 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { [Export(typeof(DiagnosticSourceManager)), Shared] - internal class DiagnosticSourceManager : IDiagnosticSourceManager + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) : IDiagnosticSourceManager { - private readonly Lazy> _sources; - private ImmutableDictionary>? _documentSources; - private ImmutableDictionary>? _workspaceSources; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager([ImportMany] Lazy> sources) - { - _sources = sources; - } + private ImmutableDictionary? _documentProviders; + private ImmutableDictionary? _workspaceProviders; /// public IEnumerable GetSourceNames(bool isDocument) { EnsureInitialized(); - return (isDocument ? _documentSources : _workspaceSources)!.Keys; + return (isDocument ? _documentProviders : _workspaceProviders)!.Keys; } /// - public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { EnsureInitialized(); - var providersDictionary = isDocument ? _documentSources : _workspaceSources; - if (!providersDictionary!.TryGetValue(sourceName, out var providers)) - { - return []; - } + var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; + if (providersDictionary!.TryGetValue(sourceName, out var provider)) + return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); - using var _ = ArrayBuilder.GetInstance(out var builder); - foreach (var provider in providers) - { - var sources = await provider.CreateDiagnosticSourcesAsync(context, sourceName, cancellationToken).ConfigureAwait(false); - builder.AddRange(sources); - } - - return builder.ToImmutableAndClear(); + return new([]); } private void EnsureInitialized() { - if (_documentSources == null || _workspaceSources == null) + if (_documentProviders == null || _workspaceProviders == null) { - Dictionary> documentSources = new(); - Dictionary> workspaceSources = new(); - foreach (var source in _sources.Value) - { - var attribute = source.GetType().GetCustomAttributes(inherit: false).FirstOrDefault(); - if (attribute != null) - { - var scopedSources = source.IsDocument ? documentSources : workspaceSources; - foreach (var sourceName in source.SourceNames) - { - if (!scopedSources.TryGetValue(sourceName, out var sources)) - { - sources = new List(); - scopedSources[sourceName] = sources; - } - ((List)sources).Add(source); - } - } - } - - var immutableSources = documentSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); - Interlocked.CompareExchange(ref _documentSources, immutableSources, null); - immutableSources = workspaceSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); - Interlocked.CompareExchange(ref _workspaceSources, immutableSources, null); + var documentProviders = sourceProviders.Value + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + Interlocked.CompareExchange(ref _documentProviders, documentProviders, null); + + var workspaceProviders = sourceProviders.Value + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + Interlocked.CompareExchange(ref _workspaceProviders, workspaceProviders, null); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 5cae97d9f3229..0a11ff5f1127f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs index 8c68d8b82094e..bcac55c7deb64 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs @@ -19,16 +19,15 @@ internal interface IDiagnosticSourceProvider bool IsDocument { get; } /// - /// Source names that this provider can provide. + /// Provider's name. Each should have a unique name within scope. /// - ImmutableArray SourceNames { get; } + string Name { get; } /// - /// Creates the diagnostic sources for the given . + /// Creates the diagnostic sources. /// /// The context. - /// Source name. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs deleted file mode 100644 index e13833fe0dbe5..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DocumentDiagnosticSourceProvider( - [Import] IGlobalOptionService globalOptions, - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : IDiagnosticSourceProvider -{ - private static readonly ImmutableArray sourceNames = - [ - PullDiagnosticCategories.Task, - PullDiagnosticCategories.DocumentCompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic - ]; - - public bool IsDocument => true; - public ImmutableArray SourceNames => sourceNames; - - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) - { - if (sourceName == PullDiagnosticCategories.Task) - return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, globalOptions)); - - if (sourceName == PullDiagnosticCategories.EditAndContinue) - { - if (GetEditAndContinueDiagnosticSource(context) is IDiagnosticSource source) - { - return new([source]); - } - - return new([]); - } - - var diagnosticKind = sourceName switch - { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - //// if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - //null => DiagnosticKind.All, // !!!VS Code does not request any diag kind!!! - // // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return new([]); - - return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind.Value, nonLocalDocumentDiagnostics: false, taskList: false, context, globalOptions)); - } - - internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) - => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; - - internal static ImmutableArray GetDiagnosticSources( - IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, bool nonLocalDocumentDiagnostics, bool taskList, RequestContext context, IGlobalOptionService globalOptions) - { - // For the single document case, that is the only doc we want to process. - // - // Note: context.Document may be null in the case where the client is asking about a document that we have - // since removed from the workspace. In this case, we don't really have anything to process. - // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. - // - // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each - // handler treats those as separate worlds that they are responsible for. - var textDocument = context.TextDocument; - if (textDocument is null) - { - context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return []; - } - - var document = textDocument as Document; - if (taskList && document is null) - { - context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); - return []; - } - - if (!context.IsTracking(textDocument.GetURI())) - { - context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return []; - } - - if (nonLocalDocumentDiagnostics) - return GetNonLocalDiagnosticSources(); - - return taskList - ? [new TaskListDiagnosticSource(document!, globalOptions)] - : [new DocumentDiagnosticSource(diagnosticAnalyzerService, diagnosticKind, textDocument)/*, Xaml source might go here; ???why doc while it should we workspace???*/]; - - ImmutableArray GetNonLocalDiagnosticSources() - { - Debug.Assert(!taskList); - - // This code path is currently only invoked from the public LSP handler, which always uses 'DiagnosticKind.All' - Debug.Assert(diagnosticKind == DiagnosticKind.All); - - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return []; - - return [new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]; - - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..52aa70dcab7a0 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() + : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is not Document document) + return new([]); + + var source = EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document); + return new([source]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..d937791b2aeff --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider : AbstractDocumentDiagnosticSourceProvider +{ + private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; + private readonly DiagnosticKind _kind; + + public AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(IDiagnosticAnalyzerService diagnosticAnalyzerService, + DiagnosticKind kind, string sourceName) : base(sourceName) + { + _diagnosticAnalyzerService = diagnosticAnalyzerService; + _kind = kind; + } + + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (GetOpenDocument(context) is not TextDocument textDocument) + return new([]); + + var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, textDocument); + return new([source]); + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSyntax) + { + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) + { + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) + { + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) + { + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8a343fabb4899 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) + : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.Task) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (GetOpenDocument(context) is not TextDocument textDocument) + return new([]); + + if (textDocument is not Document document) + { + context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); + return new([]); + } + + var source = new TaskListDiagnosticSource(document, globalOptions); + return new([source]); + } +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs index 1ef1c07135337..0880b465d18ae 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -18,19 +17,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicDocumentDiagnosticSourceProvider( - [Import] IGlobalOptionService globalOptions, - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : IDiagnosticSourceProvider + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentDiagnosticSourceProvider(All) { - public const string NonLocalSource = "nonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; - private static readonly ImmutableArray sourceNames = [NonLocalSource]; + public const string All = "All_B69807DB-28FB-4846-884A-1152E54C8B62"; - public bool IsDocument => true; - public ImmutableArray SourceNames => sourceNames; - - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - var nonLocalDocumentDiagnostics = sourceName == NonLocalSource; - var result = DocumentDiagnosticSourceProvider.GetDiagnosticSources(diagnosticAnalyzerService, DiagnosticKind.All, nonLocalDocumentDiagnostics, taskList: false, context, globalOptions); - return new(result); + var textDocument = AbstractDocumentDiagnosticSourceProvider.GetOpenDocument(context); + if (textDocument is null) + return new([]); + + var source = new DocumentDiagnosticSource(diagnosticAnalyzerService, DiagnosticKind.All /* IS THIS RIGHT ???*/, textDocument); + return new([source]); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..171fa9bb9bd55 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentDiagnosticSourceProvider(NonLocal) +{ + public const string NonLocal = "NonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; + + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + var textDocument = GetOpenDocument(context); + if (textDocument is null) + return new([]); + + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + return new([]); + + return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); + + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 9e1f3cfdd33c7..7600bc2f1ee7d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -93,10 +93,10 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) { - var sourceName = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString() - ? PublicDocumentDiagnosticSourceProvider.NonLocalSource - : string.Empty; - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + if (diagnosticParams.Identifier is string sourceName) + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + + return new([]); } protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs index d806d8e235ea0..c7e98b6187713 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs @@ -11,12 +11,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; +// FIGURE OUT WHAT IT IS SUPPOSED TO DO +/* [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicWorkspaceDiagnosticSourceProvider( [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, [""]) + : AbstractWorkspaceDiagnosticSourceProvider("") { } +*/ diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 6c72098ffaf41..013182d9e5bc3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed partial class PublicWorkspacePullDiagnosticsHandler: AbstractWorkspacePullDiagnosticsHandler, IDisposable +internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable { private readonly IClientLanguageServerManager _clientLanguageServerManager; public PublicWorkspacePullDiagnosticsHandler( diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs deleted file mode 100644 index ca21d6c822fee..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class WorkspaceDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, - [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, - [PullDiagnosticCategories.EditAndContinue, PullDiagnosticCategories.WorkspaceDocumentsAndProject]) -{ -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8c36d0905616c --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.WorkspaceDocumentsAndProject) +{ + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// + public override async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (!ShouldIgnoreContext(context)) + { + Contract.ThrowIfNull(context.Solution); + + using var _ = ArrayBuilder.GetInstance(out var result); + + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); + + foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + return result.ToImmutableAndClear(); + + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; + + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; + + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); + + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } + + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); + + return; + + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) + { + if (!ShouldSkipDocument(context, document)) + { + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); + } + } + } + + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); + } + + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } + } + + return []; + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8869ef4ccccfa --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (!ShouldIgnoreContext(context)) + { + Contract.ThrowIfNull(context.Solution); + return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..c5c18f4163c3f --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.TaskList; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.Task) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (!ShouldIgnoreContext(context)) + { + Contract.ThrowIfNull(context.Solution); + + // Only compute task list items for closed files if the option is on for it. + if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + { + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + { + foreach (var document in project.Documents) + { + if (!ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); + } + } + + return new(result.ToImmutableAndClear()); + } + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs index 7c6a0e6915c9d..cd0a235e52fa5 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -13,12 +13,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider { - internal const string SourceName = "HotReloadDiagnostic"; - internal static readonly ImmutableArray SourceNames = [SourceName]; + string IDiagnosticSourceProvider.Name => "HotReloadDiagnostic"; - ImmutableArray IDiagnosticSourceProvider.SourceNames => SourceNames; bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) => throw new NotImplementedException(); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs index 46f3f36252622..9fe8c155f3282 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -24,14 +24,12 @@ internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMan { bool IDiagnosticSourceProvider.IsDocument => true; - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { if (context.GetTrackedDocument() is { } textDocument) { if (hotReloadErrorService.Errors.FirstOrDefault(e => e.DocumentId == textDocument.Id) is { } documentErrors) - { return new([new HotReloadDiagnosticSource(textDocument, documentErrors.Errors)]); - } } return new([]); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index defca19ba4f9b..c2a08d6fed799 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -24,9 +24,9 @@ internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (sourceName != SourceName || context.Solution is not Solution solution) + if (context.Solution is not Solution solution) { return new([]); } From e3015603ce1733238891eaf829014db239dc1b2e Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Fri, 19 Apr 2024 11:49:59 -0700 Subject: [PATCH 0676/1047] Tweaks --- .../DiagnosticSourceManager.cs | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 7acc623d0e11d..8d19ed304909e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -15,45 +15,36 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { [Export(typeof(DiagnosticSourceManager)), Shared] - [method: ImportingConstructor] - [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal class DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) : IDiagnosticSourceManager + internal class DiagnosticSourceManager : IDiagnosticSourceManager { - private ImmutableDictionary? _documentProviders; - private ImmutableDictionary? _workspaceProviders; + private readonly Lazy> _documentProviders; + private readonly Lazy> _workspaceProviders; - /// - public IEnumerable GetSourceNames(bool isDocument) + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) { - EnsureInitialized(); - return (isDocument ? _documentProviders : _workspaceProviders)!.Keys; + _documentProviders = new(() => sourceProviders.Value + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); + + _workspaceProviders = new(() => sourceProviders.Value + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); } + /// + public IEnumerable GetSourceNames(bool isDocument) + => (isDocument ? _documentProviders : _workspaceProviders).Value.Keys; + /// public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { - EnsureInitialized(); var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - if (providersDictionary!.TryGetValue(sourceName, out var provider)) + if (providersDictionary.Value.TryGetValue(sourceName, out var provider)) return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); return new([]); } - - private void EnsureInitialized() - { - if (_documentProviders == null || _workspaceProviders == null) - { - var documentProviders = sourceProviders.Value - .Where(p => p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - Interlocked.CompareExchange(ref _documentProviders, documentProviders, null); - - var workspaceProviders = sourceProviders.Value - .Where(p => !p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - Interlocked.CompareExchange(ref _workspaceProviders, workspaceProviders, null); - } - } } } From 76680ae68452bf825b14c052008c529f24e836dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 12:03:41 -0700 Subject: [PATCH 0677/1047] Yield properly --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index af3f792840d2a..f4f2be82f379a 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -211,7 +211,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task GetFixLevelAsync() { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await TaskScheduler.Default; + await TaskScheduler.Default.SwitchTo(alwaysYield: true); var lowPriorityAnalyzers = new ConcurrentSet(); foreach (var order in Orderings) @@ -258,7 +258,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await TaskScheduler.Default; + await TaskScheduler.Default.SwitchTo(alwaysYield: true); if (!selection.HasValue) { From ff07e9796ed17de99b76c18053432077c8df57e1 Mon Sep 17 00:00:00 2001 From: Jonathan Yi <101259035+jonathanjyi@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:13:11 +0000 Subject: [PATCH 0678/1047] updated Microsoft.VisualStudio.Telemetry to 17.11.8 and Microsoft.VisualStudio.Utilities.Internal to 16.3.73 --- eng/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 97ff84ec7abe2..d9970c400f1f0 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -103,14 +103,14 @@ - + - + From facabce369358a84d1d95718ac9d6fa0021aad27 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 12:21:29 -0700 Subject: [PATCH 0679/1047] Use extension to simplify pattern of reading from a channel --- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index ca05cccdec70f..a7a271ff2ae70 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -150,14 +151,11 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader Date: Fri, 19 Apr 2024 12:35:32 -0700 Subject: [PATCH 0680/1047] Increase mmf file sizes based on emperical data for roslyn itself. --- .../TemporaryStorageServiceFactory.cs | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 451e15a091a56..259fa7263c73c 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -29,22 +29,23 @@ namespace Microsoft.CodeAnalysis.Host; internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal { /// - /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other - /// storage units. + /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage + /// units. /// /// - /// This value was arbitrarily chosen and appears to work well. Can be changed if data suggests - /// something better. + /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to + /// the next lower power-of-2 size for Roslyn.sln itself. /// /// - private const long SingleFileThreshold = 128 * 1024; + private const long SingleFileThreshold = 256 * 1024; /// /// The size in bytes of a memory mapped file created to store multiple temporary objects. /// /// - /// This value was arbitrarily chosen and appears to work well. Can be changed if data suggests - /// something better. + /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of + /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but + /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. /// /// private const long MultiFileBlockSize = SingleFileThreshold * 32; @@ -65,33 +66,24 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer - /// allocation until space is no longer available in it. + /// allocation until space is no longer available in it. Access should be synchronized on /// - /// - /// Access should be synchronized on . - /// private ReferenceCountedDisposable.WeakReference _weakFileReference; - /// The name of the current memory mapped file for multiple storage units. - /// - /// Access should be synchronized on . - /// + /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on + /// /// private string? _name; - /// The total size of the current memory mapped file for multiple storage units. - /// - /// Access should be synchronized on . - /// + /// The total size of the current memory mapped file for multiple storage units. Access should be + /// synchronized on /// private long _fileSize; /// - /// The offset into the current memory mapped file where the next storage unit can be held. + /// The offset into the current memory mapped file where the next storage unit can be held. Access should be + /// synchronized on . /// - /// - /// Access should be synchronized on . - /// /// private long _offset; From 8fb9484b47b2b87a314fa60a10b32ddc1999faae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 12:47:13 -0700 Subject: [PATCH 0681/1047] Remove unused async entrypoints --- .../TemporaryStorageServiceFactory.cs | 53 ++++--------------- .../TemporaryStorage/ITemporaryStorage.cs | 2 - .../TemporaryStorageServiceTests.cs | 24 ++++----- 3 files changed, 20 insertions(+), 59 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 259fa7263c73c..4fa4e5cf53697 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -357,28 +357,10 @@ public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) } } - public Task ReadStreamAsync(CancellationToken cancellationToken = default) - { - // See commentary in ReadTextAsync for why this is implemented this way. - return Task.Factory.StartNew(() => ReadStream(cancellationToken), cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken = default) - { - // The Wait() here will not actually block, since with useAsync: false, the - // entire operation will already be done when WaitStreamMaybeAsync completes. - WriteStreamMaybeAsync(stream, useAsync: false, cancellationToken: cancellationToken).GetAwaiter().GetResult(); - } - - public Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default) - => WriteStreamMaybeAsync(stream, useAsync: true, cancellationToken: cancellationToken); - - private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, CancellationToken cancellationToken) + public void WriteStream(Stream stream, CancellationToken cancellationToken) { if (_memoryMappedInfo != null) - { throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) { @@ -386,32 +368,15 @@ private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, Cancellat _memoryMappedInfo = _service.CreateTemporaryStorage(size); using var viewStream = _memoryMappedInfo.CreateWritableStream(); - var buffer = SharedPools.ByteArray.Allocate(); - try + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) { - while (true) - { - int count; - if (useAsync) - { - count = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); - } - else - { - count = stream.Read(buffer, 0, buffer.Length); - } - - if (count == 0) - { - break; - } - - viewStream.Write(buffer, 0, count); - } - } - finally - { - SharedPools.ByteArray.Free(buffer); + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index f1848b42c8618..ef647f1af7b36 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -44,7 +44,5 @@ internal interface ITemporaryTextStorageInternal : IDisposable internal interface ITemporaryStreamStorageInternal : IDisposable { Stream ReadStream(CancellationToken cancellationToken = default); - Task ReadStreamAsync(CancellationToken cancellationToken = default); void WriteStream(Stream stream, CancellationToken cancellationToken = default); - Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); } diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index bba69b5e4b499..df9924ce8f49a 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -60,8 +60,8 @@ public void TestTemporaryStorageStream() } data.Position = 0; - temporaryStorage.WriteStreamAsync(data).Wait(); - using var result = temporaryStorage.ReadStreamAsync().Result; + temporaryStorage.WriteStream(data, CancellationToken.None); + using var result = temporaryStorage.ReadStream(CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -119,18 +119,16 @@ public void TestTemporaryStreamStorageExceptions() // Nothing has been written yet Assert.Throws(() => storage.ReadStream(CancellationToken.None)); - Assert.Throws(() => storage.ReadStreamAsync().Result); // write a normal stream var stream = new MemoryStream(); stream.Write([42], 0, 1); stream.Position = 0; - storage.WriteStreamAsync(stream).Wait(); + storage.WriteStream(stream, CancellationToken.None); // Writing multiple times is not allowed // These should also throw before ever getting to the point where they would look at the null stream arg. - Assert.Throws(() => storage.WriteStream(null!)); - Assert.Throws(() => storage.WriteStreamAsync(null!).Wait()); + Assert.Throws(() => storage.WriteStream(null!, CancellationToken.None)); } [ConditionalFact(typeof(WindowsOnly))] @@ -144,7 +142,7 @@ public void TestZeroLengthStreams() // 0 length streams are allowed using (var stream1 = new MemoryStream()) { - storage.WriteStream(stream1); + storage.WriteStream(stream1, CancellationToken.None); } using (var stream2 = storage.ReadStream(CancellationToken.None)) @@ -176,7 +174,7 @@ public void TestTemporaryStorageMemoryMappedFileManagement() storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); - storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1)); + storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); @@ -220,12 +218,12 @@ public void TestTemporaryStorageScaling() var s = service.CreateTemporaryStreamStorage(); storageHandles.Add(s); data.Position = 0; - s.WriteStreamAsync(data).Wait(); + s.WriteStream(data, CancellationToken.None); } for (var i = 0; i < 1024 * 5; i++) { - using var s = storageHandles[i].ReadStreamAsync().Result; + using var s = storageHandles[i].ReadStream(CancellationToken.None); Assert.Equal(1, s.ReadByte()); storageHandles[i].Dispose(); } @@ -247,7 +245,7 @@ public void StreamTest1() } expected.Position = 0; - storage.WriteStream(expected); + storage.WriteStream(expected, CancellationToken.None); expected.Position = 0; using var stream = storage.ReadStream(CancellationToken.None); @@ -274,7 +272,7 @@ public void StreamTest2() } expected.Position = 0; - storage.WriteStream(expected); + storage.WriteStream(expected, CancellationToken.None); expected.Position = 0; using var stream = storage.ReadStream(CancellationToken.None); @@ -316,7 +314,7 @@ public void StreamTest3() } expected.Position = 0; - storage.WriteStream(expected); + storage.WriteStream(expected, CancellationToken.None); expected.Position = 0; using var stream = storage.ReadStream(CancellationToken.None); From 03db600b62bdc3e9c2dc33734d77d5d36e2dd66d Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 19 Apr 2024 14:34:10 -0700 Subject: [PATCH 0682/1047] Watch razor and cshtml files for devkit (#73077) DevKit does not get a loaded project, so we need to watch for razor/cshtml file changes manually in the ProjectSystemProject. --- .../ProjectSystem/ProjectSystemProject.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 6249e92df4226..4f387f594bed4 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -191,21 +191,30 @@ internal ProjectSystemProject( _filePath = filePath; _parseOptions = parseOptions; - var fileExtensionToWatch = language switch { LanguageNames.CSharp => ".cs", LanguageNames.VisualBasic => ".vb", _ => null }; + var watchedDirectories = GetWatchedDirectories(language, filePath); + _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(watchedDirectories); + _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; - if (filePath != null && fileExtensionToWatch != null) - { - // Since we have a project directory, we'll just watch all the files under that path; that'll avoid extra overhead of - // having to add explicit file watches everywhere. - var projectDirectoryToWatch = new WatchedDirectory(Path.GetDirectoryName(filePath)!, fileExtensionToWatch); - _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(projectDirectoryToWatch); - } - else + static WatchedDirectory[] GetWatchedDirectories(string? language, string? filePath) { - _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(); - } + if (filePath is null) + { + return []; + } - _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; + var rootPath = Path.GetDirectoryName(filePath); + if (rootPath is null) + { + return []; + } + + return language switch + { + LanguageNames.VisualBasic => [new(rootPath, ".vb")], + LanguageNames.CSharp => [new(rootPath, ".cs"), new(rootPath, ".razor"), new(rootPath, ".cshtml")], + _ => [] + }; + } } private void ChangeProjectProperty(ref T field, T newValue, Func updateSolution, bool logThrowAwayTelemetry = false) From 72398f0cd5498f9ea9991d3b78b1bf2be4bce0cb Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Fri, 19 Apr 2024 14:45:07 -0700 Subject: [PATCH 0683/1047] Refining HotReloadDiagnostics API --- .../DiagnosticSourceManager.cs | 6 +- .../Contracts/IHotReloadDiagnosticManager.cs | 19 +++-- .../Contracts/IHotReloadDiagnosticSource.cs | 26 +++++++ ...cumentHotReloadDiagnosticSourceProvider.cs | 12 ++- .../Internal/HotReloadDiagnosticManager.cs | 78 +++---------------- .../Internal/HotReloadDiagnosticSource.cs | 13 ++-- ...kspaceHotReloadDiagnosticSourceProvider.cs | 17 ++-- .../InternalAPI.Unshipped.txt | 14 ++-- 8 files changed, 86 insertions(+), 99 deletions(-) create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 8d19ed304909e..1db563fed292b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -14,16 +14,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - [Export(typeof(DiagnosticSourceManager)), Shared] + [Export(typeof(IDiagnosticSourceManager)), Shared] internal class DiagnosticSourceManager : IDiagnosticSourceManager { private readonly Lazy> _documentProviders; private readonly Lazy> _workspaceProviders; + private readonly Lazy> sourceProviders; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) + public DiagnosticSourceManager(/*[ImportMany] Lazy> sourceProviders*/) { + sourceProviders = new(() => new List()); _documentProviders = new(() => sourceProviders.Value .Where(p => p.IsDocument) .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs index aa48b2ea23197..3e601bb176a98 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -9,20 +9,23 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts internal interface IHotReloadDiagnosticManager { /// - /// Hot reload errors. + /// Hot reload diagnostics for all sources. /// - ImmutableArray Errors { get; } + ImmutableArray Sources { get; } /// - /// Update the diagnostics for the given group name. + /// Registers source of hot reload diagnostics. /// - /// The diagnostics. - /// The group name. - void UpdateErrors(ImmutableArray errors, string groupName); + void Register(IHotReloadDiagnosticSource source); /// - /// Clears all errors. + /// Unregisters source of hot reload diagnostics. /// - void Clear(); + void Unregister(IHotReloadDiagnosticSource source); + + /// + /// Requests refresh of hot reload diagnostics. + /// + void Refresh(); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..661ecc1f05724 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + /// + /// Source for hot reload diagnostics. + /// + internal interface IHotReloadDiagnosticSource + { + /// + /// Provides list of document ids that have hot reload diagnostics. + /// + ValueTask> GetDocumentIdsAsync(CancellationToken cancellationToken); + + /// + /// Provides list of diagnostics for the given document. + /// + ValueTask> GetDocumentDiagnosticsAsync(TextDocument document, CancellationToken cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs index 9fe8c155f3282..16d9e109d4741 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -18,7 +19,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) +internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadDiagnosticManager) : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { @@ -28,8 +29,13 @@ ValueTask> IDiagnosticSourceProvider.CreateDia { if (context.GetTrackedDocument() is { } textDocument) { - if (hotReloadErrorService.Errors.FirstOrDefault(e => e.DocumentId == textDocument.Id) is { } documentErrors) - return new([new HotReloadDiagnosticSource(textDocument, documentErrors.Errors)]); + List sources = new(); + foreach (var hotReloadSource in hotReloadDiagnosticManager.Sources) + { + sources.Add(new HotReloadDiagnosticSource(textDocument, hotReloadSource)); + } + + return new(sources.ToImmutableArray()); } return new([]); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index 14b54af628827..cdd278715f947 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -3,13 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using System.Collections.Immutable; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; using Microsoft.CodeAnalysis.Host.Mef; -using System.Collections.Generic; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; @@ -18,78 +16,24 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class HotReloadDiagnosticManager(IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { - private ImmutableDictionary> _errors = ImmutableDictionary>.Empty; - private ImmutableArray? _allErrors = null; + private ImmutableArray _sources = ImmutableArray.Empty; - void IHotReloadDiagnosticManager.UpdateErrors(ImmutableArray errors, string groupName) - { - errors = errors.RemoveAll(d => d.Errors.IsEmpty); + ImmutableArray IHotReloadDiagnosticManager.Sources => _sources; + void IHotReloadDiagnosticManager.Refresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); - var oldErrors = _errors; - if (errors.IsEmpty) - { - _errors = _errors.Remove(groupName); - } - else - { - _errors = _errors.SetItem(groupName, errors); - } - - if (_errors != oldErrors) - { - _allErrors = null; - diagnosticsRefresher.RequestWorkspaceRefresh(); - } - } - - void IHotReloadDiagnosticManager.Clear() + void IHotReloadDiagnosticManager.Register(IHotReloadDiagnosticSource source) { - if (!_errors.IsEmpty) + // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. + if (!_sources.Contains(source)) { - _errors = ImmutableDictionary>.Empty; - _allErrors = ImmutableArray.Empty; - diagnosticsRefresher.RequestWorkspaceRefresh(); + _sources = _sources.Add(source); } } - ImmutableArray IHotReloadDiagnosticManager.Errors + void IHotReloadDiagnosticManager.Unregister(IHotReloadDiagnosticSource source) { - get - { - _allErrors ??= ComputeAllErrors(_errors); - return _allErrors.Value; - } - } - - private static ImmutableArray ComputeAllErrors(ImmutableDictionary> errors) - { - if (errors.Count == 0) - { - return ImmutableArray.Empty; - } - - if (errors.Count == 1) - { - return errors.First().Value; - } - - var allErrors = new Dictionary>(); - foreach (var group in errors.Values) - { - foreach (var documentErrors in group) - { - if (!allErrors.TryGetValue(documentErrors.DocumentId, out var list)) - { - list = new List(); - allErrors.Add(documentErrors.DocumentId, list); - } - - list.AddRange(documentErrors.Errors); - } - } - - return allErrors - .Where(kvp => kvp.Value.Count > 0) - .Select(kvp => new HotReloadDocumentDiagnostics(kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableArray(); + // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. + _sources = _sources.Remove(source); + diagnosticsRefresher.RequestWorkspaceRefresh(); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index c11487fd93070..5d41c5a98860e 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -2,27 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Roslyn.LanguageServer.Protocol; -using Microsoft.CodeAnalysis.LanguageServer; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal { - internal class HotReloadDiagnosticSource(TextDocument document, ImmutableArray errors) : IDiagnosticSource + internal class HotReloadDiagnosticSource(TextDocument document, IHotReloadDiagnosticSource hotReloadDiagnosticSource) : IDiagnosticSource { - Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { - var result = errors.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); - return Task.FromResult(result); + var diagnostics = await hotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return result; } TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index c2a08d6fed799..742fba39407f5 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -24,24 +24,27 @@ internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { if (context.Solution is not Solution solution) { - return new([]); + return []; } using var _ = ArrayBuilder.GetInstance(out var builder); - foreach (var documentErrors in hotReloadErrorService.Errors) + foreach (var hotReloadSource in hotReloadErrorService.Sources) { - TextDocument? document = solution.GetAdditionalDocument(documentErrors.DocumentId) ?? solution.GetDocument(documentErrors.DocumentId); - if (document != null && !context.IsTracking(document.GetURI())) + var docIds = await hotReloadSource.GetDocumentIdsAsync(cancellationToken).ConfigureAwait(false); + foreach (var docId in docIds) { - builder.Add(new HotReloadDiagnosticSource(document, documentErrors.Errors)); + if (solution.GetDocument(docId) is { } document && !context.IsTracking(document.GetURI())) + { + builder.Add(new HotReloadDiagnosticSource(document, hotReloadSource)); + } } } var result = builder.ToImmutableAndClear(); - return new(result); + return result; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 9deff7bbdd06d..439b114134052 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -4,9 +4,13 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocum Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Errors.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray errors) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Clear() -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Errors.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.UpdateErrors(System.Collections.Immutable.ImmutableArray errors, string! groupName) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Refresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Sources.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(Microsoft.CodeAnalysis.TextDocument! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentIdsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! @@ -15,11 +19,11 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnos Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.AbstractHotReloadDiagnosticSourceProvider() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadDiagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! hotReloadDiagnosticSource) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry From 6700d1175ba4ceb4031be885408dd8bb99ce659c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 14:51:04 -0700 Subject: [PATCH 0684/1047] Add main concepts --- .../ITemporaryStorageService.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 694b26ba147b0..d18d46a67b20a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; +using System.Runtime.Serialization; using System.Threading; namespace Microsoft.CodeAnalysis.Host; @@ -16,6 +18,40 @@ public interface ITemporaryStorageService : IWorkspaceService internal interface ITemporaryStorageServiceInternal : IWorkspaceService { - ITemporaryStreamStorageInternal CreateTemporaryStreamStorage(); + TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken); + ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } + +/// +/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is +/// not disposed, the data should remain in storage and can be readable from any process using the information provided +/// in . Use to write +/// the data to temporary storage and get a handle to it. Use to read the data back in any process. +/// +internal sealed class TemporaryStorageHandle : IDisposable +{ + private IDisposable? _underlyingData; + private TemporaryStorageIdentifier? _identifier; + + public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + + public void Dispose() + { + var data = Interlocked.Exchange(ref _underlyingData, null); + data?.Dispose(); + _identifier = null; + } +} + +/// +/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be +/// used to identify that segment across processes, allowing for efficient sharing of data. +/// +[DataContract] +internal sealed record TemporaryStorageIdentifier( + [property: DataMember(Order = 0)] string Name, + [property: DataMember(Order = 1)] int Offset, + [property: DataMember(Order = 2)] int Length); From dae552bd22f7d03fb72e58c0212dded6b2771746 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 14:55:25 -0700 Subject: [PATCH 0685/1047] trivial impl --- .../ITemporaryStorageService.cs | 6 +-- .../TrivialTemporaryStorageService.cs | 47 +++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index d18d46a67b20a..01fca76eac673 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -31,10 +31,10 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// the data to temporary storage and get a handle to it. Use to read the data back in any process. /// -internal sealed class TemporaryStorageHandle : IDisposable +internal sealed class TemporaryStorageHandle(IDisposable underlyingData, TemporaryStorageIdentifier identifier) : IDisposable { - private IDisposable? _underlyingData; - private TemporaryStorageIdentifier? _identifier; + private IDisposable? _underlyingData = underlyingData; + private TemporaryStorageIdentifier? _identifier = identifier; public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 0f3641b459ca7..8cf30a633ee24 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -4,11 +4,12 @@ using System; using System.IO; -using System.Text; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -16,16 +17,34 @@ internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceI { public static readonly TrivialTemporaryStorageService Instance = new(); + private static ConditionalWeakTable s_streamStorage = new(); + private TrivialTemporaryStorageService() { } - public ITemporaryStreamStorageInternal CreateTemporaryStreamStorage() - => new StreamStorage(); - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() => new TextStorage(); + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var storage = new StreamStorage(); + storage.WriteStream(stream, cancellationToken); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); + var handle = new TemporaryStorageHandle(storage, identifier); + + return handle; + } + + public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + { + Contract.ThrowIfFalse( + s_streamStorage.TryGetValue(storageIdentifier, out var streamStorage), + "StorageIdentifier was not created by this storage service!"); + + return streamStorage.ReadStream(cancellationToken); + } + private sealed class StreamStorage : ITemporaryStreamStorageInternal { private MemoryStream? _stream; @@ -45,11 +64,6 @@ public Stream ReadStream(CancellationToken cancellationToken) return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); } - public Task ReadStreamAsync(CancellationToken cancellationToken) - { - return Task.FromResult(ReadStream(cancellationToken)); - } - public void WriteStream(Stream stream, CancellationToken cancellationToken) { var newStream = new MemoryStream(); @@ -60,21 +74,6 @@ public void WriteStream(Stream stream, CancellationToken cancellationToken) throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); } } - - public async Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken) - { - var newStream = new MemoryStream(); -#if NETCOREAPP - await stream.CopyToAsync(newStream, cancellationToken).ConfigureAwait(false); -# else - await stream.CopyToAsync(newStream).ConfigureAwait(false); -#endif - var existingValue = Interlocked.CompareExchange(ref _stream, newStream, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } } private sealed class TextStorage : ITemporaryTextStorageInternal From 7ee761f2f3fae009e0359fd4db575bf4e48c6489 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 14:58:41 -0700 Subject: [PATCH 0686/1047] Main impl --- .../TemporaryStorageServiceFactory.cs | 28 +++++++++---------- .../ITemporaryStorageService.cs | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 4fa4e5cf53697..92c68193bb2b7 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -101,14 +101,19 @@ public TemporaryTextStorage AttachTemporaryTextStorage( string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - ITemporaryStreamStorageInternal ITemporaryStorageServiceInternal.CreateTemporaryStreamStorage() - => CreateTemporaryStreamStorage(); - - internal TemporaryStreamStorage CreateTemporaryStreamStorage() - => new(this); + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this); + storage.WriteStream(stream, cancellationToken); + var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); + return new(storage, identifier); + } - public TemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, long offset, long size) - => new(this, storageName, offset, size); + public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); + return storage.ReadStream(cancellationToken); + } /// /// Allocate shared storage of a specified size. @@ -310,7 +315,7 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM } } - internal sealed class TemporaryStreamStorage : ITemporaryStreamStorageInternal, ITemporaryStorageWithName + internal sealed class TemporaryStreamStorage { private readonly TemporaryStorageService _service; private MemoryMappedInfo? _memoryMappedInfo; @@ -324,9 +329,7 @@ public TemporaryStreamStorage(TemporaryStorageService service, string storageNam _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); } - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size is only used when Name is not null. - public string? Name => _memoryMappedInfo?.Name; + public string Name => _memoryMappedInfo!.Name; public long Offset => _memoryMappedInfo!.Offset; public long Size => _memoryMappedInfo!.Size; @@ -339,9 +342,6 @@ public void Dispose() _memoryMappedInfo = null; } - Stream ITemporaryStreamStorageInternal.ReadStream(CancellationToken cancellationToken) - => ReadStream(cancellationToken); - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) { if (_memoryMappedInfo == null) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 01fca76eac673..e35b3cfcdd73b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -53,5 +53,5 @@ public void Dispose() [DataContract] internal sealed record TemporaryStorageIdentifier( [property: DataMember(Order = 0)] string Name, - [property: DataMember(Order = 1)] int Offset, - [property: DataMember(Order = 2)] int Length); + [property: DataMember(Order = 1)] long Offset, + [property: DataMember(Order = 2)] long Size); From 8ea948af186ef49ee1d554f7104b73884664ab29 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:03:01 -0700 Subject: [PATCH 0687/1047] Update skeletons --- ...CompilationState.SkeletonReferenceCache.cs | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index f0e1ffc395db2..ef93f263e5108 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,11 +218,16 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var storage = TryCreateMetadataStorage(services, compilation, cancellationToken); - if (storage == null) + var temporaryStorageService = services.GetRequiredService(); + + var handle = TryCreateMetadataStorage(); + if (handle == null) return null; - var metadata = AssemblyMetadata.CreateFromStream(storage.ReadStream(cancellationToken), leaveOpen: false); + // Now read the data back from the stream from the memory mapped file. This will come back as an + // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. + var metadata = AssemblyMetadata.CreateFromStream( + temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); // read in the stream and pass ownership of it to the metadata object. When it is disposed it will dispose // the stream as well. @@ -230,62 +235,63 @@ public readonly SkeletonReferenceCache Clone() metadata, compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - } - - private static ITemporaryStreamStorageInternal? TryCreateMetadataStorage(SolutionServices services, Compilation compilation, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var logger = services.GetService(); - try + TemporaryStorageHandle? TryCreateMetadataStorage() { - logger?.Log($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); + cancellationToken.ThrowIfCancellationRequested(); - using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) - { - using var stream = SerializableBytes.CreateWritableStream(); + var logger = services.GetService(); - var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + try + { + logger?.Log($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); - if (emitResult.Success) + using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + using var stream = SerializableBytes.CreateWritableStream(); - var temporaryStorageService = services.GetRequiredService(); - var storage = temporaryStorageService.CreateTemporaryStreamStorage(); + // First, emit the data to an in-memory stream. + var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - stream.Position = 0; - storage.WriteStream(stream, cancellationToken); + if (emitResult.Success) + { + logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); - return storage; - } + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + stream.Position = 0; + var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - if (logger != null) - { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + return handle; + } - foreach (var diagnostic in emitResult.Diagnostics) + if (logger != null) { - logger.Log(" " + diagnostic.GetMessage()); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); + } } - } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; + return null; + } + } + finally + { + logger?.Log($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); } - } - finally - { - logger?.Log($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); } } } From 330f53c587c47fa5146cdf59ab2d955ce8392555 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:10:50 -0700 Subject: [PATCH 0688/1047] in progress --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f39c0d444450b..d0a694c0284ae 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -451,7 +451,7 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial return metadata; } - private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( + private (TemporaryStorageHandle handle, long length) GetTemporaryStorage( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); From 02c8d56098e4f2e297595374cc0a822e40ea9379 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:14:57 -0700 Subject: [PATCH 0689/1047] in progress --- .../SerializerService_Reference.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index d0a694c0284ae..a0e8c3fbbf584 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -23,8 +23,6 @@ internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; - private static readonly ConditionalWeakTable s_lifetimeMap = new(); - public static Checksum CreateChecksum(MetadataReference reference, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) @@ -440,13 +438,16 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial Contract.ThrowIfFalse(SerializationKinds.Bits == kind); var array = reader.ReadByteArray(); - var pinnedObject = new PinnedObject(array); - var metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), array.Length); + // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. + var pinnedObject = new PinnedObject(array); - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - s_lifetimeMap.Add(metadata, pinnedObject); + // The pinned object will be kept alive as long as the metadata is alive due to us passing in + // pinnedObject.Dispose as the onDispose callback. So we do not have to worry about the GC preemptively + // cleaning things up while the metadata is alive. Nor do we have to worry about the pinned object being + // leaked, locking the bytes in place. + var metadata = ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); return metadata; } @@ -487,17 +488,15 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static void GetMetadata(Stream stream, long length, out ModuleMetadata metadata, out object? lifeTimeObject) + private static ModuleMetadata GetMetadata(Stream stream, long length) { if (stream is UnmanagedMemoryStream unmanagedStream) { // For an unmanaged memory stream, ModuleMetadata can take ownership directly. unsafe { - metadata = ModuleMetadata.CreateFromMetadata( + return ModuleMetadata.CreateFromMetadata( (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - lifeTimeObject = null; - return; } } @@ -515,8 +514,7 @@ private static void GetMetadata(Stream stream, long length, out ModuleMetadata m pinnedObject = new PinnedObject(array); } - metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length); - lifeTimeObject = pinnedObject; + return ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) From 6533c3411f397045697ed2144cc55704aaab1439 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:18:38 -0700 Subject: [PATCH 0690/1047] Simplify lifetime management of ModuleMetadata instances created from pointers to managed memory --- .../SerializerService_Reference.cs | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f39c0d444450b..bcfbc4d34d1e7 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -23,8 +23,6 @@ internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; - private static readonly ConditionalWeakTable s_lifetimeMap = new(); - public static Checksum CreateChecksum(MetadataReference reference, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) @@ -425,12 +423,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var storageStream = storage.ReadStream(cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); - GetMetadata(storageStream, length, out var metadata, out var lifeTimeObject); - - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - if (lifeTimeObject != null) - s_lifetimeMap.Add(metadata, lifeTimeObject); + var metadata = GetMetadata(storageStream, length); return (metadata, storage); } @@ -442,13 +435,10 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial var array = reader.ReadByteArray(); var pinnedObject = new PinnedObject(array); - var metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), array.Length); - - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - s_lifetimeMap.Add(metadata, pinnedObject); - - return metadata; + // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in + // as the onDispose callback of the metadata. + return ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( @@ -487,17 +477,17 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static void GetMetadata(Stream stream, long length, out ModuleMetadata metadata, out object? lifeTimeObject) + private static ModuleMetadata GetMetadata(Stream stream, long length) { if (stream is UnmanagedMemoryStream unmanagedStream) { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. unsafe { - metadata = ModuleMetadata.CreateFromMetadata( + return ModuleMetadata.CreateFromMetadata( (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - lifeTimeObject = null; - return; } } @@ -515,8 +505,10 @@ private static void GetMetadata(Stream stream, long length, out ModuleMetadata m pinnedObject = new PinnedObject(array); } - metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length); - lifeTimeObject = pinnedObject; + // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in + // as the onDispose callback of the metadata. + return ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) From 8bbd01c3da50c90f7d9b908ea421b820a2306448 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:32:57 -0700 Subject: [PATCH 0691/1047] in progress --- .../Host/TemporaryStorage/ITemporaryStorage.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index ef647f1af7b36..0e51a34da6993 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -41,8 +41,8 @@ internal interface ITemporaryTextStorageInternal : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -internal interface ITemporaryStreamStorageInternal : IDisposable -{ - Stream ReadStream(CancellationToken cancellationToken = default); - void WriteStream(Stream stream, CancellationToken cancellationToken = default); -} +//internal interface ITemporaryStreamStorageInternal : IDisposable +//{ +// Stream ReadStream(CancellationToken cancellationToken = default); +// void WriteStream(Stream stream, CancellationToken cancellationToken = default); +//} From 9d2b53738aa59b5de5c49320113d27813c99614b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:48:06 -0700 Subject: [PATCH 0692/1047] Remove solution replicatoin context --- .../Portable/Remote/ISerializerService.cs | 2 +- .../Serialization/SerializableSourceText.cs | 4 +-- .../Serialization/SerializerService.cs | 8 ++--- .../Serialization/SerializerService_Asset.cs | 8 ++--- .../SerializerService_Reference.cs | 8 ++--- .../SolutionReplicationContext.cs | 31 ------------------- .../Workspace/Solution/Checksum_Factory.cs | 14 +++------ .../Fakes/SimpleAssetSource.cs | 3 +- .../Remote/TestSerializerService.cs | 4 +-- .../Remote/Core/RemoteHostAssetWriter.cs | 2 +- .../Remote/Core/SolutionAssetStorage.Scope.cs | 6 ---- .../Remote/Core/SolutionAssetStorage.cs | 2 -- 12 files changed, 22 insertions(+), 70 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs diff --git a/src/Workspaces/Core/Portable/Remote/ISerializerService.cs b/src/Workspaces/Core/Portable/Remote/ISerializerService.cs index f6f74f5bae648..f56f5f0a89417 100644 --- a/src/Workspaces/Core/Portable/Remote/ISerializerService.cs +++ b/src/Workspaces/Core/Portable/Remote/ISerializerService.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Serialization; internal interface ISerializerService : IWorkspaceService { - void Serialize(object value, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken); + void Serialize(object value, ObjectWriter writer, CancellationToken cancellationToken); void SerializeParseOptions(ParseOptions options, ObjectWriter writer); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index ad9ded5d6bd10..86bec3e6decad 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -139,13 +139,11 @@ public static ValueTask FromTextDocumentStateAsync( } } - public void Serialize(ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_storage is not null) { - context.AddResource(_storage); - writer.WriteInt32((int)_storage.ChecksumAlgorithm); writer.WriteEncoding(_storage.Encoding); writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storage.ContentHash)!); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index e263fdad2c23b..02868db1077ea 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -75,7 +75,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken case WellKnownSynchronizationKind.ParseOptions: case WellKnownSynchronizationKind.ProjectReference: case WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity: - return Checksum.Create(value, this); + return Checksum.Create(value, this, cancellationToken); case WellKnownSynchronizationKind.MetadataReference: return CreateChecksum((MetadataReference)value, cancellationToken); @@ -94,7 +94,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken } } - public void Serialize(object value, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public void Serialize(object value, ObjectWriter writer, CancellationToken cancellationToken) { var kind = value.GetWellKnownSynchronizationKind(); @@ -134,7 +134,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return; case WellKnownSynchronizationKind.MetadataReference: - SerializeMetadataReference((MetadataReference)value, writer, context, cancellationToken); + SerializeMetadataReference((MetadataReference)value, writer, cancellationToken); return; case WellKnownSynchronizationKind.AnalyzerReference: @@ -142,7 +142,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return; case WellKnownSynchronizationKind.SerializableSourceText: - SerializeSourceText((SerializableSourceText)value, writer, context, cancellationToken); + SerializeSourceText((SerializableSourceText)value, writer, cancellationToken); return; case WellKnownSynchronizationKind.SolutionCompilationState: diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs index c3842c9ff3870..d7bbeba322659 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs @@ -17,9 +17,9 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal partial class SerializerService { - private static void SerializeSourceText(SerializableSourceText text, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + private static void SerializeSourceText(SerializableSourceText text, ObjectWriter writer, CancellationToken cancellationToken) { - text.Serialize(writer, context, cancellationToken); + text.Serialize(writer, cancellationToken); } private void SerializeCompilationOptions(CompilationOptions options, ObjectWriter writer, CancellationToken cancellationToken) @@ -86,10 +86,10 @@ private static ProjectReference DeserializeProjectReference(ObjectReader reader, return new ProjectReference(projectId, aliases.ToImmutableArrayOrEmpty(), embedInteropTypes); } - private void SerializeMetadataReference(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + private void SerializeMetadataReference(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - WriteMetadataReferenceTo(reference, writer, context, cancellationToken); + WriteMetadataReferenceTo(reference, writer, cancellationToken); } private MetadataReference DeserializeMetadataReference(ObjectReader reader, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index bcfbc4d34d1e7..c5e19134e2309 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -60,13 +60,13 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT return Checksum.Create(stream); } - public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) { if (portable is ISupportTemporaryStorage supportTemporaryStorage) { - if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, context, cancellationToken)) + if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, cancellationToken)) { return; } @@ -311,7 +311,7 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio } private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - ISupportTemporaryStorage reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) { var storages = reference.GetStorages(); if (storages == null) @@ -329,8 +329,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return false; } - context.AddResource(storage); - pooled.Object.Add((storage2.Name, storage2.Offset, storage2.Size)); } diff --git a/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs b/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs deleted file mode 100644 index e1cb61be7218f..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Serialization; - -internal readonly struct SolutionReplicationContext : IDisposable -{ - private static readonly ObjectPool> s_pool = new(() => []); - - private readonly ConcurrentSet _resources; - - public SolutionReplicationContext() - => _resources = s_pool.Allocate(); - - public void AddResource(IDisposable resource) - => _resources.Add(resource); - - public void Dispose() - { - // TODO: https://github.com/dotnet/roslyn/issues/49973 - // Currently we don't dispose resources, only keep them alive. - // Shouldn't we dispose them? - // _resources.All(resource => resource.Dispose()); - s_pool.ClearAndFree(_resources); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs index 95519835568d7..ead02e2f244d2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs @@ -105,16 +105,12 @@ public static Checksum Create(ImmutableArray bytes) writer.WriteByte(b); }); - public static Checksum Create(T value, ISerializerService serializer) - { - using var context = new SolutionReplicationContext(); - - return Create( - (value, serializer, context), + public static Checksum Create(T value, ISerializerService serializer, CancellationToken cancellationToken) + => Create( + (value, serializer, cancellationToken), static (tuple, writer) => { - var (value, serializer, context) = tuple; - serializer.Serialize(value!, writer, context, CancellationToken.None); + var (value, serializer, cancellationToken) = tuple; + serializer.Serialize(value!, writer, cancellationToken); }); - } } diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index ab8478a29d58e..e9e24ba94cab1 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs @@ -26,11 +26,10 @@ public ValueTask GetAssetsAsync( Contract.ThrowIfFalse(map.TryGetValue(checksum, out var data)); using var stream = new MemoryStream(); - using var context = new SolutionReplicationContext(); using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - serializerService.Serialize(data, writer, context, cancellationToken); + serializerService.Serialize(data, writer, cancellationToken); } stream.Position = 0; diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index 8c42582dd37cc..bf86ae8934251 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -41,7 +41,7 @@ public TestSerializerService(ConcurrentDictionary _sharedTestGeneratorReferences = sharedTestGeneratorReferences; } - public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { var wellKnownReferenceName = s_wellKnownReferenceNames.GetValueOrDefault(reference, null); if (wellKnownReferenceName is not null) @@ -52,7 +52,7 @@ public override void WriteMetadataReferenceTo(MetadataReference reference, Objec else { writer.WriteBoolean(false); - base.WriteMetadataReferenceTo(reference, writer, context, cancellationToken); + base.WriteMetadataReferenceTo(reference, writer, cancellationToken); } } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index ca05cccdec70f..27cdfea93180d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -219,7 +219,7 @@ private void WriteAssetToTempStream( objectWriter.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); // Now serialize out the asset itself. - _serializer.Serialize(asset, objectWriter, _scope.ReplicationContext, cancellationToken); + _serializer.Serialize(asset, objectWriter, cancellationToken); } private void WriteSentinelByteToPipeWriter() diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index acc4b58edc971..845f823512bf4 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -25,12 +25,6 @@ internal sealed partial class Scope( public readonly ProjectCone? ProjectCone = projectCone; public readonly SolutionCompilationState CompilationState = compilationState; - /// - /// Will be disposed from when the last ref-count to this scope goes - /// away. - /// - public readonly SolutionReplicationContext ReplicationContext = new(); - /// /// Only safe to read write while is held. /// diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 8e87958c46eb3..d5f80f5030c92 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -111,8 +111,6 @@ private void DecreaseScopeRefCount(Scope scope) // Last ref went away, update our maps while under the lock, then cleanup its context data outside of the lock. _checksumToScope.Remove(solutionChecksum); } - - scope.ReplicationContext.Dispose(); } internal TestAccessor GetTestAccessor() From ebd529ee1f8c697ebc9aa0f59f0e6a7f68d48be5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:55:26 -0700 Subject: [PATCH 0693/1047] Fix --- .../MetadataReferences/VisualStudioMetadataReferenceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 8fe74c01b94e9..a679387a69a87 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -214,7 +214,7 @@ void GetStorageInfoFromTemporaryStorage( storage = _temporaryStorageService.CreateTemporaryStreamStorage(); copyStream.Position = 0; - storage.WriteStream(copyStream); + storage.WriteStream(copyStream, CancellationToken.None); } // get stream that owns the underlying unmanaged memory. From d5328b5a1a33008e00d15ee40ac8da5a4a831ad3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:56:29 -0700 Subject: [PATCH 0694/1047] Fix --- .../MetadataReferences/VisualStudioMetadataReferenceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 8fe74c01b94e9..a679387a69a87 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -214,7 +214,7 @@ void GetStorageInfoFromTemporaryStorage( storage = _temporaryStorageService.CreateTemporaryStreamStorage(); copyStream.Position = 0; - storage.WriteStream(copyStream); + storage.WriteStream(copyStream, CancellationToken.None); } // get stream that owns the underlying unmanaged memory. From 5a6b83554f1d977bb7e687e8b6127b210337f3b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 18:11:00 -0700 Subject: [PATCH 0695/1047] In progress --- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 23 ++------- .../ITemporaryStreamStorageExtensions.cs | 47 ------------------- .../TrivialTemporaryStorageService.cs | 12 ++--- .../ProjectSystemProjectOptionsProcessor.cs | 46 +++++++++++++++--- 5 files changed, 50 insertions(+), 80 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index e17bcb1a26d4f..d0d18406c8333 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -13,5 +13,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? GetStorages(); + IReadOnlyList? GetStorageIdentifiers(); } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 9439bccd2e83d..bf66d91062e9f 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -313,31 +313,16 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) { - var storages = reference.GetStorages(); - if (storages == null) - { + var storageIdentifiers = reference.GetStorageIdentifiers(); + if (storageIdentifiers == null) return false; - } - - // Not clear if name should be allowed to be null here (https://github.com/dotnet/roslyn/issues/43037) - using var pooled = Creator.CreateList<(string? name, long offset, long size)>(); - - foreach (var storage in storages) - { - if (storage is not ITemporaryStorageWithName storage2) - { - return false; - } - - pooled.Object.Add((storage2.Name, storage2.Offset, storage2.Size)); - } WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); - writer.WriteInt32(pooled.Object.Count); + writer.WriteInt32(storageIdentifiers.Count); - foreach (var (name, offset, size) in pooled.Object) + foreach (var (name, offset, size) in storageIdentifiers) { writer.WriteInt32((int)MetadataImageKind.Module); writer.WriteString(name); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs deleted file mode 100644 index af0d841bea463..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; - -namespace Microsoft.CodeAnalysis.Host; - -internal static class ITemporaryStreamStorageExtensions -{ - public static void WriteAllLines(this ITemporaryStreamStorageInternal storage, ImmutableArray values) - { - using var stream = SerializableBytes.CreateWritableStream(); - using var writer = new StreamWriter(stream); - - foreach (var value in values) - { - writer.WriteLine(value); - } - - writer.Flush(); - stream.Position = 0; - - storage.WriteStream(stream); - } - - public static ImmutableArray ReadLines(this ITemporaryStreamStorageInternal storage) - { - return EnumerateLines(storage).ToImmutableArray(); - } - - private static IEnumerable EnumerateLines(ITemporaryStreamStorageInternal storage) - { - using var stream = storage.ReadStream(); - using var reader = new StreamReader(stream); - - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 8cf30a633ee24..de638eb1272de 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -17,7 +17,7 @@ internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceI { public static readonly TrivialTemporaryStorageService Instance = new(); - private static ConditionalWeakTable s_streamStorage = new(); + private static readonly ConditionalWeakTable s_streamStorage = new(); private TrivialTemporaryStorageService() { @@ -29,7 +29,7 @@ public ITemporaryTextStorageInternal CreateTemporaryTextStorage() public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { var storage = new StreamStorage(); - storage.WriteStream(stream, cancellationToken); + storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); var handle = new TemporaryStorageHandle(storage, identifier); @@ -42,10 +42,10 @@ public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storage s_streamStorage.TryGetValue(storageIdentifier, out var streamStorage), "StorageIdentifier was not created by this storage service!"); - return streamStorage.ReadStream(cancellationToken); + return streamStorage.ReadStream(); } - private sealed class StreamStorage : ITemporaryStreamStorageInternal + private sealed class StreamStorage : IDisposable { private MemoryStream? _stream; @@ -55,7 +55,7 @@ public void Dispose() _stream = null; } - public Stream ReadStream(CancellationToken cancellationToken) + public Stream ReadStream() { var stream = _stream ?? throw new InvalidOperationException(); @@ -64,7 +64,7 @@ public Stream ReadStream(CancellationToken cancellationToken) return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); } - public void WriteStream(Stream stream, CancellationToken cancellationToken) + public void WriteStream(Stream stream) { var newStream = new MemoryStream(); stream.CopyTo(newStream); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 11be0ef802128..04fcee98897cd 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -3,8 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Threading; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; @@ -36,7 +39,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private ITemporaryStreamStorageInternal? _commandLineStorage; + private TemporaryStorageHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -71,12 +74,20 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments // Dispose the existing stored command-line and then persist the new one so we can // recover it later. Only bother persisting things if we have a non-empty string. - _commandLineStorage?.Dispose(); - _commandLineStorage = null; + _commandLineStorageHandle?.Dispose(); + _commandLineStorageHandle = null; if (!arguments.IsEmpty) { - _commandLineStorage = _temporaryStorageService.CreateTemporaryStreamStorage(); - _commandLineStorage.WriteAllLines(arguments); + using var stream = SerializableBytes.CreateWritableStream(); + using var writer = new StreamWriter(stream); + + foreach (var value in arguments) + writer.WriteLine(value); + + writer.Flush(); + stream.Position = 0; + + _commandLineStorageHandle = _temporaryStorageService.WriteToTemporaryStorage(stream, CancellationToken.None); } ReparseCommandLine_NoLock(arguments); @@ -237,12 +248,33 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // effective values was potentially done by the act of parsing the command line. Even though the command line didn't change textually, // the effective result did. Then we call UpdateProjectOptions_NoLock to reapply any values; that will also re-acquire the new ruleset // includes in the IDE so we can be watching for changes again. - var commandLine = _commandLineStorage == null ? ImmutableArray.Empty : _commandLineStorage.ReadLines(); + var commandLine = _commandLineStorageHandle == null + ? ImmutableArray.Empty + : ReadLines(_temporaryStorageService, _commandLineStorageHandle.Identifier); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); UpdateProjectOptions_NoLock(); } + + static ImmutableArray ReadLines( + ITemporaryStorageServiceInternal temporaryStorageService, + TemporaryStorageIdentifier storageIdentifier) + { + return EnumerateLines(temporaryStorageService, storageIdentifier).ToImmutableArray(); + } + + static IEnumerable EnumerateLines( + ITemporaryStorageServiceInternal temporaryStorageService, + TemporaryStorageIdentifier storageIdentifier) + { + using var stream = temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); + using var reader = new StreamReader(stream); + + string? line; + while ((line = reader.ReadLine()) != null) + yield return line; + } } /// @@ -276,7 +308,7 @@ public void Dispose() lock (_gate) { DisposeOfRuleSetFile_NoLock(); - _commandLineStorage?.Dispose(); + _commandLineStorageHandle?.Dispose(); } } } From 287f83671948d26561bf617b137369c494c70e4b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 18:23:31 -0700 Subject: [PATCH 0696/1047] In progress --- .../SerializerService_Reference.cs | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index bf66d91062e9f..162f070175bf3 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -333,7 +333,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return true; } - private (Metadata metadata, ImmutableArray storages)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageIdentifiers)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -361,19 +361,19 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT #pragma warning restore CA2016 } - return (AssemblyMetadata.Create(pooledMetadata.Object), storages: default); + return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: default); } Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); #pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storages: default); + return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: default); #pragma warning restore CA2016 } if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); - using var pooledStorage = Creator.CreateList(); + using var pooledStorageIdentifiers = Creator.CreateList(); var count = reader.ReadInt32(); for (var i = 0; i < count; i++) @@ -381,13 +381,13 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT metadataKind = (MetadataImageKind)reader.ReadInt32(); Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - var (metadata, storage) = ReadModuleMetadataFrom(reader, kind, cancellationToken); + var (metadata, storageIdentifier) = ReadModuleMetadataFrom(reader, kind, cancellationToken); pooledMetadata.Object.Add(metadata); - pooledStorage.Object.Add(storage); + pooledStorageIdentifiers.Object.Add(storageIdentifier); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorage.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorageIdentifiers.Object.ToImmutableArrayOrEmpty()); } Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); @@ -396,7 +396,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return (moduleInfo.metadata, ImmutableArray.Create(moduleInfo.storage)); } - private (ModuleMetadata metadata, ITemporaryStreamStorageInternal storage) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifeir) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -426,39 +426,33 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } - private (TemporaryStorageHandle handle, long length) GetTemporaryStorage( + private (TemporaryStorageIdentifier handle, long length) GetTemporaryStorageIdentifier( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); if (kind == SerializationKinds.Bits) { - var storage = _storageService.CreateTemporaryStreamStorage(); + // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the + // server side so that we can refer to this data uniformly. using var stream = SerializableBytes.CreateWritableStream(); - CopyByteArrayToStream(reader, stream, cancellationToken); var length = stream.Length; stream.Position = 0; - storage.WriteStream(stream, cancellationToken); + var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - return (storage, length); + return (storageHandle.Identifier, length); } else { - var service2 = (TemporaryStorageService)_storageService; - + // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it + // will not be released by the host. var name = reader.ReadRequiredString(); var offset = reader.ReadInt64(); var size = reader.ReadInt64(); - -#pragma warning disable CA1416 // Validate platform compatibility - var storage = service2.AttachTemporaryStreamStorage(name, offset, size); -#pragma warning restore CA1416 // Validate platform compatibility - var length = size; - - return (storage, length); + return (new TemporaryStorageIdentifier(name, offset, size), size); } } From b28dcb8238a8a592351d0947d926e06e8a9c08c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 18:26:45 -0700 Subject: [PATCH 0697/1047] In progress --- .../Serialization/SerializerService_Reference.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 162f070175bf3..9e15099f1640c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -393,22 +393,26 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, ImmutableArray.Create(moduleInfo.storage)); + return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); } - private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifeir) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var (storage, length) = GetTemporaryStorage(reader, kind, cancellationToken); + // Get the storage identifier for the module metadata. + var (storageIdentifier, length) = GetTemporaryStorageIdentifier(reader, kind, cancellationToken); - var storageStream = storage.ReadStream(cancellationToken); + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); var metadata = GetMetadata(storageStream, length); - return (metadata, storage); + return (metadata, storageIdentifier); } private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) From e3315975eefcb1d38ac09e24b5c4dac397550669 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:12:53 -0700 Subject: [PATCH 0698/1047] In progress --- .../VisualStudioMetadataReferenceManager.cs | 28 +++++++++---------- .../TemporaryStorageServiceFactory.cs | 7 +++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index a679387a69a87..866a914ab79e6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -43,7 +43,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// the remote process, and it can map that same memory in directly, instead of needing the host to send the /// entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorages = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageIdentifiers = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -86,12 +86,12 @@ public void Dispose() } } - public IReadOnlyList? GetStorages(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageIdentifiers(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorages.TryGetValue(source, out var storages)) + s_metadataToStorageIdentifiers.TryGetValue(source, out var storages)) { return storages; } @@ -154,19 +154,18 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storages); + using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); var newMetadata = CreateAssemblyMetadata(key, key => { // // - GetMetadataFromTemporaryStorage(key, out var storage, out var metadata); - storages.Add(storage); + GetMetadataFromTemporaryStorage(key, out var storageIdentifier, out var metadata); + storageIdentifiers.Add(storageIdentifier); return metadata; }); - var storagesArray = storages.ToImmutable(); - s_metadataToStorages.Add(newMetadata, storagesArray); + s_metadataToStorageIdentifiers.Add(newMetadata, storageIdentifiers.ToImmutable()); return newMetadata; } @@ -174,9 +173,9 @@ AssemblyMetadata GetMetadataWorker() } private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out ModuleMetadata metadata) + FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out ModuleMetadata metadata) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storage, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageIdentifier, out var stream); unsafe { @@ -187,7 +186,7 @@ private void GetMetadataFromTemporaryStorage( return; void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out UnmanagedMemoryStream stream) { int size; using (var copyStream = SerializableBytes.CreateWritableStream()) @@ -211,14 +210,13 @@ void GetStorageInfoFromTemporaryStorage( } // copy over the data to temp storage and let pooled stream go - storage = _temporaryStorageService.CreateTemporaryStreamStorage(); - copyStream.Position = 0; - storage.WriteStream(copyStream, CancellationToken.None); + var handle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); + storageIdentifier = handle.Identifier; } // get stream that owns the underlying unmanaged memory. - stream = storage.ReadStream(CancellationToken.None); + stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 92c68193bb2b7..365a2870ab1cb 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -109,7 +109,10 @@ public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, Cancellatio return new(storage, identifier); } - public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) { var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); return storage.ReadStream(cancellationToken); @@ -315,7 +318,7 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM } } - internal sealed class TemporaryStreamStorage + internal sealed class TemporaryStreamStorage : IDisposable { private readonly TemporaryStorageService _service; private MemoryMappedInfo? _memoryMappedInfo; From b3cd483053c94fb7132eb1d4291b9e94f29d719a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:23:17 -0700 Subject: [PATCH 0699/1047] In progress --- .../VisualStudioMetadataReference.Snapshot.cs | 4 +-- .../VisualStudioMetadataReferenceManager.cs | 3 +-- .../SerializerService_Reference.cs | 27 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 8b8d1b5f0cc5f..98423db28fab4 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList GetStorages() - => _provider.GetStorages(this.FilePath, _timestamp.Value); + public IReadOnlyList GetStorageIdentifiers() + => _provider.GetStorageIdentifiers(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 866a914ab79e6..280a5ec1c30cf 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -154,7 +154,7 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); + using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); var newMetadata = CreateAssemblyMetadata(key, key => { // @@ -164,7 +164,6 @@ AssemblyMetadata GetMetadataWorker() return metadata; }); - s_metadataToStorageIdentifiers.Add(newMetadata, storageIdentifiers.ToImmutable()); return newMetadata; diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 9e15099f1640c..eecf1079e1e95 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -233,8 +233,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe var filePath = reader.ReadString(); - var tuple = TryReadMetadataFrom(reader, kind, cancellationToken); - if (tuple == null) + if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageIdentifiers)) { // TODO: deal with xml document provider properly // should we shadow copy xml doc comment? @@ -254,7 +253,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe _documentationService.GetDocumentationProvider(filePath) : XmlDocumentationProvider.Default; return new SerializedMetadataReference( - properties, filePath, tuple.Value.metadata, tuple.Value.storages, documentProvider); + properties, filePath, metadata, storageIdentifiers, documentProvider); } private static void WriteTo(MetadataReferenceProperties properties, ObjectWriter writer, CancellationToken cancellationToken) @@ -361,12 +360,12 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT #pragma warning restore CA2016 } - return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: default); + return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: []); } Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); #pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: default); + return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); #pragma warning restore CA2016 } @@ -604,16 +603,20 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storagesOpt; + private readonly ImmutableArray _storageIdentifiers; private readonly DocumentationProvider _provider; public SerializedMetadataReference( - MetadataReferenceProperties properties, string? fullPath, - Metadata metadata, ImmutableArray storagesOpt, DocumentationProvider initialDocumentation) + MetadataReferenceProperties properties, + string? fullPath, + Metadata metadata, + ImmutableArray storagesIdentifiers, + DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { + Contract.ThrowIfTrue(storagesIdentifiers.IsDefault); _metadata = metadata; - _storagesOpt = storagesOpt; + _storageIdentifiers = storagesIdentifiers; _provider = initialDocumentation; } @@ -628,9 +631,9 @@ protected override Metadata GetMetadataImpl() => _metadata; protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new SerializedMetadataReference(properties, FilePath, _metadata, _storagesOpt, _provider); + => new SerializedMetadataReference(properties, FilePath, _metadata, _storageIdentifiers, _provider); - public IReadOnlyList? GetStorages() - => _storagesOpt.IsDefault ? null : _storagesOpt; + public IReadOnlyList? GetStorageIdentifiers() + => _storageIdentifiers; } } From 37d5b17ef9231d4f080525c7019121dc465f97d6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:25:45 -0700 Subject: [PATCH 0700/1047] Fix --- .../Test.Next/Remote/SerializationValidator.cs | 3 +-- .../Remote/SnapshotSerializationTests.cs | 16 ++++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index cf37f02b4b2ae..632755a0b1a82 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -94,11 +94,10 @@ public async Task GetValueAsync(Checksum checksum) var data = await GetRequiredAssetAsync(checksum).ConfigureAwait(false); Contract.ThrowIfNull(data.Value); - using var context = new SolutionReplicationContext(); using var stream = SerializableBytes.CreateWritableStream(); using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - Serializer.Serialize(data.Value, writer, context, CancellationToken.None); + Serializer.Serialize(data.Value, writer, CancellationToken.None); } stream.Position = 0; diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index e06ebecbeef94..f4f5d370c3350 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -611,11 +611,9 @@ public void TestEncodingSerialization() var serializableSourceText = new SerializableSourceText(sourceText, sourceText.GetContentHash()); using (var stream = SerializableBytes.CreateWritableStream()) { - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(serializableSourceText, objectWriter, context, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -631,11 +629,9 @@ public void TestEncodingSerialization() serializableSourceText = new SerializableSourceText(sourceText, sourceText.GetContentHash()); using (var stream = SerializableBytes.CreateWritableStream()) { - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(serializableSourceText, objectWriter, context, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -662,11 +658,9 @@ public void TestCompilationOptions_NullableAndImport() void VerifyOptions(CompilationOptions originalOptions) { using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(originalOptions, objectWriter, context, CancellationToken.None); + serializer.Serialize(originalOptions, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -683,11 +677,9 @@ void VerifyOptions(CompilationOptions originalOptions) private static SolutionAsset CloneAsset(ISerializerService serializer, SolutionAsset asset) { using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(asset.Value, writer, context, CancellationToken.None); + serializer.Serialize(asset.Value, writer, CancellationToken.None); } stream.Position = 0; From f37ede48f17a9c71cabb2cad40fd897f93dcafcb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:36:24 -0700 Subject: [PATCH 0701/1047] Fixup tests --- .../TemporaryStorageServiceTests.cs | 74 ++++++------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index df9924ce8f49a..9da9edc7d6aa8 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -51,7 +51,6 @@ public void TestTemporaryStorageStream() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var temporaryStorage = service.CreateTemporaryStreamStorage(); using var data = SerializableBytes.CreateWritableStream(); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -60,8 +59,9 @@ public void TestTemporaryStorageStream() } data.Position = 0; - temporaryStorage.WriteStream(data, CancellationToken.None); - using var result = temporaryStorage.ReadStream(CancellationToken.None); + var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); + + using var result = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -109,43 +109,21 @@ public void TestTemporaryTextStorageExceptions() Assert.Throws(() => storage.WriteTextAsync(text).Wait()); } - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryStreamStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadStream(CancellationToken.None)); - - // write a normal stream - var stream = new MemoryStream(); - stream.Write([42], 0, 1); - stream.Position = 0; - storage.WriteStream(stream, CancellationToken.None); - - // Writing multiple times is not allowed - // These should also throw before ever getting to the point where they would look at the null stream arg. - Assert.Throws(() => storage.WriteStream(null!, CancellationToken.None)); - } - [ConditionalFact(typeof(WindowsOnly))] public void TestZeroLengthStreams() { using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); // 0 length streams are allowed + TemporaryStorageHandle handle; using (var stream1 = new MemoryStream()) { - storage.WriteStream(stream1, CancellationToken.None); + handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); } - using (var stream2 = storage.ReadStream(CancellationToken.None)) + using (var stream2 = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None)) { Assert.Equal(0, stream2.Length); } @@ -168,19 +146,17 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - using ITemporaryStreamStorageInternal storage1 = service.CreateTemporaryStreamStorage(), - storage2 = service.CreateTemporaryStreamStorage(); - var storage3 = service.CreateTemporaryStreamStorage(); // let the finalizer run for this instance + using var storage1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); + using var storage2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); - storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); - storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); - storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); + // let the finalizer run for this instance + var storage3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); - using Stream s1 = storage1.ReadStream(), - s2 = storage2.ReadStream(), - s3 = storage3.ReadStream(CancellationToken.None); + using Stream s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); + using Stream s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); + using Stream s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); @@ -212,18 +188,17 @@ public void TestTemporaryStorageScaling() // Create 4GB of memory mapped files var fileCount = (int)((long)4 * 1024 * 1024 * 1024 / data.Length); - var storageHandles = new List(fileCount); + var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { - var s = service.CreateTemporaryStreamStorage(); - storageHandles.Add(s); data.Position = 0; - s.WriteStream(data, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); + storageHandles.Add(handle); } for (var i = 0; i < 1024 * 5; i++) { - using var s = storageHandles[i].ReadStream(CancellationToken.None); + using var s = service.ReadFromTemporaryStorageService(storageHandles[i].Identifier, CancellationToken.None); Assert.Equal(1, s.ReadByte()); storageHandles[i].Dispose(); } @@ -236,7 +211,6 @@ public void StreamTest1() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); for (var i = 0; i < 10000; i++) @@ -245,10 +219,10 @@ public void StreamTest1() } expected.Position = 0; - storage.WriteStream(expected, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) @@ -263,7 +237,6 @@ public void StreamTest2() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); for (var i = 0; i < 10000; i++) @@ -272,10 +245,10 @@ public void StreamTest2() } expected.Position = 0; - storage.WriteStream(expected, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(expected.Length, stream.Length); var index = 0; @@ -300,7 +273,6 @@ public void StreamTest3() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); var random = new Random(Environment.TickCount); @@ -314,10 +286,10 @@ public void StreamTest3() } expected.Position = 0; - storage.WriteStream(expected, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) From a02864b134cbef853a0bc0f438c03007bf418305 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:46:37 -0700 Subject: [PATCH 0702/1047] lint --- .../Core/Test.Next/Remote/SnapshotSerializationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index f4f5d370c3350..d638391df2977 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -613,7 +613,7 @@ public void TestEncodingSerialization() { using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; From c4bd6cf78cc90841d449fefd338c2428048577de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:17:12 -0700 Subject: [PATCH 0703/1047] Docs --- .../ITemporaryStorageService.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index e35b3cfcdd73b..3b75d7170fceb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -16,9 +16,39 @@ public interface ITemporaryStorageService : IWorkspaceService ITemporaryTextStorage CreateTemporaryTextStorage(CancellationToken cancellationToken = default); } +/// +/// API to allow a client to write data to memory-mapped-file storage (allowing it to be shared across processes). +/// internal interface ITemporaryStorageServiceInternal : IWorkspaceService { + /// + /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can + /// be used to identify the data across processes allowing it to be read back in in any process. Note: the data + /// will not longer be readonable if the returned is disposed. + /// + /// + /// This type is used for two purposes. + /// + /// + /// Dumping metadata to disk. This then allowing them to be read in by mapping + /// their data into types like . It also allows them to be read in by our server + /// process, without having to transmit the data over the wire. For this use case, we never dispose of the handle, + /// opting to keep things simple by having the host and server not have to coordinate on the lifetime of the data. + /// + /// + /// Dumping large compiler command lines to disk to purge them from main memory. Some of these strings are enormous + /// (many MB large), and will get into the LOH. This allows us to dump the data, knowing we can perfectly + /// reconstruct it when needed. In this case, we do dispose of the handle, as we don't need to keep the data around + /// when we get the next large compiler command line. + /// + /// + /// TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + + /// + /// Reads the data indicated to by the into a stream. This stream can be + /// created in a different process than the one that wrote the data originally. + /// Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken); ITemporaryTextStorageInternal CreateTemporaryTextStorage(); From 3dd37c9f135148348bc5bddd9592ee9801a394bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:17:24 -0700 Subject: [PATCH 0704/1047] lint --- .../Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 3b75d7170fceb..2ac827f2263e2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -66,7 +66,7 @@ internal sealed class TemporaryStorageHandle(IDisposable underlyingData, Tempora private IDisposable? _underlyingData = underlyingData; private TemporaryStorageIdentifier? _identifier = identifier; - public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); public void Dispose() { From 558560e7f5995d7f612adfd8fcfba830c807243e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:20:27 -0700 Subject: [PATCH 0705/1047] docs --- .../VisualStudioMetadataReferenceManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 280a5ec1c30cf..80b5dd3670027 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -37,11 +37,11 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS private static readonly ConditionalWeakTable s_lifetimeMap = new(); /// - /// Mapping from an we created, to the memory mapped files (mmf) corresponding to - /// the assembly and all the modules within it. This is kept around to make OOP syncing more efficient. - /// Specifically, since we know we read the assembly into an mmf, we can just send the mmf name/offset/length to - /// the remote process, and it can map that same memory in directly, instead of needing the host to send the - /// entire contents of the assembly over the channel to the OOP process. + /// Mapping from an we created, to the identifiers identifying the memory mapped + /// files (mmf) corresponding to that assembly and all the modules within it. This is kept around to make OOP + /// syncing more efficient. Specifically, since we know we dumped the assembly into an mmf, we can just send the mmf + /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the + /// host to send the entire contents of the assembly over the channel to the OOP process. /// private static readonly ConditionalWeakTable> s_metadataToStorageIdentifiers = new(); From 9ec14a8d70d124f5c7187f4e0bd03268f4f42b34 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:26:50 -0700 Subject: [PATCH 0706/1047] comments --- .../VisualStudioMetadataReferenceManager.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 80b5dd3670027..637aa33983542 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -188,9 +188,12 @@ void GetStorageInfoFromTemporaryStorage( FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out UnmanagedMemoryStream stream) { int size; + + // Create a temp stream in memory to copy the metadata bytes into. using (var copyStream = SerializableBytes.CreateWritableStream()) { - // open a file and let it go as soon as possible + // Open a file on disk, find the metadata section, copy those bytes into the temp stream, and release + // the file immediately after. using (var fileStream = FileUtilities.OpenRead(moduleFileKey.FullPath)) { var headers = new PEHeaders(fileStream); @@ -208,13 +211,15 @@ void GetStorageInfoFromTemporaryStorage( StreamCopy(fileStream, copyStream, offset, size); } - // copy over the data to temp storage and let pooled stream go + // Now, copy over the metadata bytes into a memory mapped file. This will keep it fixed in a single + // location, so we can create a metadata value wrapping that. This will also let us share the memory + // for that metadata value with our OOP process. copyStream.Position = 0; var handle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); storageIdentifier = handle.Identifier; } - // get stream that owns the underlying unmanaged memory. + // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); // stream size must be same as what metadata reader said the size should be. From a32fa8e580d0f58448287b478091c0223f5cdd54 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:31:36 -0700 Subject: [PATCH 0707/1047] inline --- .../Serialization/SerializerService.cs | 10 +++- .../SerializerService_Reference.cs | 46 ++++--------------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 02868db1077ea..88cc09e2e4a45 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; @@ -16,6 +17,9 @@ namespace Microsoft.CodeAnalysis.Serialization; +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif internal partial class SerializerService : ISerializerService { [ExportWorkspaceServiceFactory(typeof(ISerializerService), layer: ServiceLayer.Default), Shared] @@ -36,7 +40,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly SolutionServices _workspaceServices; - private readonly ITemporaryStorageServiceInternal _storageService; + private readonly TemporaryStorageService _storageService; private readonly ITextFactoryService _textService; private readonly IDocumentationProviderService? _documentationService; private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider; @@ -48,7 +52,9 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - _storageService = workspaceServices.GetRequiredService(); + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // storage service here is well known. + _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); _documentationService = workspaceServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index eecf1079e1e95..f7392defd3a4e 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -409,9 +409,15 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); - var metadata = GetMetadata(storageStream, length); - - return (metadata, storageIdentifier); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + return (metadata, storageIdentifier); + } } private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) @@ -459,40 +465,6 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static ModuleMetadata GetMetadata(Stream stream, long length) - { - if (stream is UnmanagedMemoryStream unmanagedStream) - { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - return ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - } - } - - PinnedObject pinnedObject; - if (stream is MemoryStream memory && - memory.TryGetBuffer(out var buffer) && - buffer.Offset == 0) - { - pinnedObject = new PinnedObject(buffer.Array!); - } - else - { - var array = new byte[length]; - stream.Read(array, 0, (int)length); - pinnedObject = new PinnedObject(array); - } - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); - } - private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); From 8c564823fc5bf2b5cfad7e31326eb42334631c9f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:40:52 -0700 Subject: [PATCH 0708/1047] Specialize for windows --- .../TestTemporaryStorageServiceFactory.cs | 42 +++++++++---------- .../SolutionTests/SolutionTestHelpers.cs | 9 +--- .../CoreTest/SyntaxReferenceTests.cs | 8 +--- .../CoreTest/TestCompositionTests.cs | 19 ++++++++- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs index 21147f43736bd..74e33409cdb15 100644 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs @@ -1,24 +1,24 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; +//using System; +//using System.Composition; +//using Microsoft.CodeAnalysis.Host; +//using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.UnitTests.Persistence -{ - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } +//namespace Microsoft.CodeAnalysis.UnitTests.Persistence +//{ +// [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] +// internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory +// { +// [ImportingConstructor] +// [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +// public TestTemporaryStorageServiceFactory() +// { +// } - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } -} +// public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) +// => TrivialTemporaryStorageService.Instance; +// } +//} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index 0cdc9229c893f..0ae9c1400866d 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests @@ -17,17 +16,11 @@ internal static class SolutionTestHelpers public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost testHost = TestHost.InProcess) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); - public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); - public static Workspace CreateWorkspaceWithRecoverableText() => CreateWorkspace(); public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) - => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics([typeof(TestTemporaryStorageServiceFactory)], testHost); + => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); #nullable disable diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index 2ce0a35a4bb70..68323e087a197 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -6,11 +6,8 @@ using System; using System.Linq; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; using CS = Microsoft.CodeAnalysis.CSharp; @@ -26,10 +23,7 @@ private static Workspace CreateWorkspace(Type[] additionalParts = null) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + => CreateWorkspace(); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 13237a0dcb699..32535180dc45a 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,14 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { + [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] + internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestTemporaryStorageServiceFactory() + { + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => TrivialTemporaryStorageService.Instance; + } + public class TestCompositionTests { [Fact] From c36d7f56573ce372dc9e9e78ce4cc187bc2e7b43 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:46:17 -0700 Subject: [PATCH 0709/1047] Doc --- .../Core/Portable/Serialization/ISupportTemporaryStorage.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index d0d18406c8333..757551a84198d 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -8,8 +8,9 @@ namespace Microsoft.CodeAnalysis.Serialization; /// -/// This lets consumer to get to inner temporary storage that references use -/// as its shadow copy storage +/// Interface for services that support dumping their contents to memory-mapped-files (generally speaking, our assembly +/// reference objects). This allows those objects to expose the memory-mapped-file info needed to read that data back +/// in in any process. /// internal interface ISupportTemporaryStorage { From f00c59f2f6d3d42d6968e72818cf387328c86929 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:48:21 -0700 Subject: [PATCH 0710/1047] null or empty --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f7392defd3a4e..02e01f40211b2 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -313,7 +313,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) { var storageIdentifiers = reference.GetStorageIdentifiers(); - if (storageIdentifiers == null) + if (storageIdentifiers is null || storageIdentifiers.Count == 0) return false; WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); From a1bfa7bff730c386dd03e914c49413f4301ad26c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:50:28 -0700 Subject: [PATCH 0711/1047] In progress --- .../SerializerService_Reference.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 02e01f40211b2..8c1281964bd46 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -362,11 +362,13 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: []); } - - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); + else + { + Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); #pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); + return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); #pragma warning restore CA2016 + } } if (metadataKind == MetadataImageKind.Assembly) @@ -388,11 +390,13 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorageIdentifiers.Object.ToImmutableArrayOrEmpty()); } + else + { + Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - - var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); + var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); + return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); + } } private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFrom( From ca76aa88369cdeaae397dc76b363284b23a28e68 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:51:03 -0700 Subject: [PATCH 0712/1047] Simplify --- .../SerializerService_Reference.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 8c1281964bd46..41bf453acb1cb 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -343,34 +343,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } var metadataKind = (MetadataImageKind)imageKind; - if (_storageService == null) - { - if (metadataKind == MetadataImageKind.Assembly) - { - using var pooledMetadata = Creator.CreateList(); - - var count = reader.ReadInt32(); - for (var i = 0; i < count; i++) - { - metadataKind = (MetadataImageKind)reader.ReadInt32(); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - pooledMetadata.Object.Add(ReadModuleMetadataFrom(reader, kind)); -#pragma warning restore CA2016 - } - - return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: []); - } - else - { - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); -#pragma warning restore CA2016 - } - } - if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); From 8141930bee4f536a91f3fa0d7d165e9afce92f4b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:56:11 -0700 Subject: [PATCH 0713/1047] simplify --- ...CompilationState.SkeletonReferenceCache.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index ef93f263e5108..5a80a4c713714 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,17 +218,10 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var temporaryStorageService = services.GetRequiredService(); - - var handle = TryCreateMetadataStorage(); - if (handle == null) + var metadata = TryCreateMetadata(); + if (metadata == null) return null; - // Now read the data back from the stream from the memory mapped file. This will come back as an - // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. - var metadata = AssemblyMetadata.CreateFromStream( - temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); - // read in the stream and pass ownership of it to the metadata object. When it is disposed it will dispose // the stream as well. return new SkeletonReferenceSet( @@ -236,7 +229,7 @@ public readonly SkeletonReferenceCache Clone() compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - TemporaryStorageHandle? TryCreateMetadataStorage() + AssemblyMetadata? TryCreateMetadata() { cancellationToken.ThrowIfCancellationRequested(); @@ -261,9 +254,14 @@ public readonly SkeletonReferenceCache Clone() // assembly-metadata point directly to that pointer in memory, instead of it having to make its // own copy it needs to own the lifetime of. stream.Position = 0; + + var temporaryStorageService = services.GetRequiredService(); var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - return handle; + // Now read the data back from the stream from the memory mapped file. This will come back as an + // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. + return AssemblyMetadata.CreateFromStream( + temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); } if (logger != null) From 4bf45a4ab4801b5d03d18f5bc21fe341385600b0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:58:24 -0700 Subject: [PATCH 0714/1047] remove --- .../TestTemporaryStorageServiceFactory.cs | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs deleted file mode 100644 index 74e33409cdb15..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System; -//using System.Composition; -//using Microsoft.CodeAnalysis.Host; -//using Microsoft.CodeAnalysis.Host.Mef; - -//namespace Microsoft.CodeAnalysis.UnitTests.Persistence -//{ -// [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] -// internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory -// { -// [ImportingConstructor] -// [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -// public TestTemporaryStorageServiceFactory() -// { -// } - -// public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) -// => TrivialTemporaryStorageService.Instance; -// } -//} From 8f9a9a5e7b0d3dc78493ce11fc8ca069414428aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 21:00:42 -0700 Subject: [PATCH 0715/1047] Simplify --- .../Solution/SolutionCompilationState.SkeletonReferenceCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 5a80a4c713714..1700598bcb8d6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -234,6 +234,7 @@ public readonly SkeletonReferenceCache Clone() cancellationToken.ThrowIfCancellationRequested(); var logger = services.GetService(); + var temporaryStorageService = services.GetRequiredService(); try { @@ -255,7 +256,6 @@ public readonly SkeletonReferenceCache Clone() // own copy it needs to own the lifetime of. stream.Position = 0; - var temporaryStorageService = services.GetRequiredService(); var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); // Now read the data back from the stream from the memory mapped file. This will come back as an From 71b3ea5b51f750cd327d7b1003cdf18e6c7ba7ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 21:08:00 -0700 Subject: [PATCH 0716/1047] Simplify --- .../SerializerService_Reference.cs | 28 +++++---- ...CompilationState.SkeletonReferenceCache.cs | 63 ++++++++++--------- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 41bf453acb1cb..4dac07640da70 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -377,13 +377,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT cancellationToken.ThrowIfCancellationRequested(); // Get the storage identifier for the module metadata. - var (storageIdentifier, length) = GetTemporaryStorageIdentifier(reader, kind, cancellationToken); - - // Now read in the module data using that identifier. This will either be reading from the host's memory if - // they passed us the information about that memory segment. Or it will be reading from our own memory if they - // sent us the full contents. - var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(length == storageStream.Length); + var (storageIdentifier, storageStream) = GetTemporaryStorageData(reader, kind, cancellationToken); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of @@ -411,11 +405,13 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } - private (TemporaryStorageIdentifier handle, long length) GetTemporaryStorageIdentifier( + private (TemporaryStorageIdentifier identifier, UnmanagedMemoryStream storageStream) GetTemporaryStorageData( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); + long length; + TemporaryStorageIdentifier storageIdentifier; if (kind == SerializationKinds.Bits) { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the @@ -423,12 +419,11 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial using var stream = SerializableBytes.CreateWritableStream(); CopyByteArrayToStream(reader, stream, cancellationToken); - var length = stream.Length; + length = stream.Length; stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - - return (storageHandle.Identifier, length); + storageIdentifier = storageHandle.Identifier; } else { @@ -436,9 +431,16 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial // will not be released by the host. var name = reader.ReadRequiredString(); var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); - return (new TemporaryStorageIdentifier(name, offset, size), size); + length = reader.ReadInt64(); + storageIdentifier = new TemporaryStorageIdentifier(name, offset, length); } + + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(length == storageStream.Length); + return (storageIdentifier, storageStream); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 1700598bcb8d6..6c2c4f62f6b52 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -242,48 +242,53 @@ public readonly SkeletonReferenceCache Clone() using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - using var stream = SerializableBytes.CreateWritableStream(); - - // First, emit the data to an in-memory stream. - var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - - if (emitResult.Success) + TemporaryStorageHandle? handle = null; + EmitResult emitResult; + using (var stream = SerializableBytes.CreateWritableStream()) { - logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // First, emit the data to an in-memory stream. + emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the - // assembly-metadata point directly to that pointer in memory, instead of it having to make its - // own copy it needs to own the lifetime of. - stream.Position = 0; - - var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); + if (emitResult.Success) + { + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + stream.Position = 0; + handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); + } + } + if (handle != null) + { // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. return AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); } - - if (logger != null) + else { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); - - foreach (var diagnostic in emitResult.Diagnostics) + if (logger != null) { - logger.Log(" " + diagnostic.GetMessage()); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); + } } - } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; + return null; + } } } finally From 72a4937ca29421b58e13022decb46d2900b09686 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 21:10:30 -0700 Subject: [PATCH 0717/1047] Add back logging --- .../Solution/SolutionCompilationState.SkeletonReferenceCache.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 6c2c4f62f6b52..ae82c81236ce3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -261,6 +261,8 @@ public readonly SkeletonReferenceCache Clone() if (handle != null) { + logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. return AssemblyMetadata.CreateFromStream( From 2f371042b9c01551e62883487a973f4936c9922f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:16:17 -0700 Subject: [PATCH 0718/1047] use property' git push --- .../VisualStudioMetadataReference.Snapshot.cs | 2 +- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 29 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 98423db28fab4..711aa80dcd26e 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList GetStorageIdentifiers() + public IReadOnlyList StorageIdentifiers => _provider.GetStorageIdentifiers(this.FilePath, _timestamp.Value); } } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index 757551a84198d..d622b8fd39162 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? GetStorageIdentifiers(); + IReadOnlyList? StorageIdentifiers { get; }; } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 4dac07640da70..f63af77354508 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -62,17 +62,16 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { - if (reference is PortableExecutableReference portable) + if (reference is PortableExecutableReference peReference) { - if (portable is ISupportTemporaryStorage supportTemporaryStorage) + if (peReference is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && + TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( + peReference, storageIdentifiers, writer, cancellationToken)) { - if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, cancellationToken)) - { - return; - } + return; } - WritePortableExecutableReferenceTo(portable, writer, cancellationToken); + WritePortableExecutableReferenceTo(peReference, writer, cancellationToken); return; } @@ -310,13 +309,14 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio } private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) + PortableExecutableReference reference, + IReadOnlyList storageIdentifiers, + ObjectWriter writer, + CancellationToken cancellationToken) { - var storageIdentifiers = reference.GetStorageIdentifiers(); - if (storageIdentifiers is null || storageIdentifiers.Count == 0) - return false; + Contract.ThrowIfTrue(storageIdentifiers.Count == 0); - WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); + WritePortableExecutableReferenceHeaderTo(reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); writer.WriteInt32(storageIdentifiers.Count); @@ -556,6 +556,8 @@ private sealed class SerializedMetadataReference : PortableExecutableReference, private readonly ImmutableArray _storageIdentifiers; private readonly DocumentationProvider _provider; + public IReadOnlyList StorageIdentifiers => _storageIdentifiers; + public SerializedMetadataReference( MetadataReferenceProperties properties, string? fullPath, @@ -582,8 +584,5 @@ protected override Metadata GetMetadataImpl() protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) => new SerializedMetadataReference(properties, FilePath, _metadata, _storageIdentifiers, _provider); - - public IReadOnlyList? GetStorageIdentifiers() - => _storageIdentifiers; } } From a8b0152cf73976103f204fa50dff3dfe08d98b28 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:19:48 -0700 Subject: [PATCH 0719/1047] Cleanup --- .../Core/Portable/Serialization/ISupportTemporaryStorage.cs | 2 +- .../Workspace/Host/TemporaryStorage/ITemporaryStorage.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index d622b8fd39162..2eaee88847d46 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageIdentifiers { get; }; + IReadOnlyList? StorageIdentifiers { get; } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index 0e51a34da6993..6ae03ba1b3a0c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -40,9 +40,3 @@ internal interface ITemporaryTextStorageInternal : IDisposable void WriteText(SourceText text, CancellationToken cancellationToken = default); Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } - -//internal interface ITemporaryStreamStorageInternal : IDisposable -//{ -// Stream ReadStream(CancellationToken cancellationToken = default); -// void WriteStream(Stream stream, CancellationToken cancellationToken = default); -//} From a5106195698fb700b73300e6c9fc9d831adf5798 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:22:49 -0700 Subject: [PATCH 0720/1047] inline more --- .../ProjectSystemProjectOptionsProcessor.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 04fcee98897cd..b2ea809fa6420 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -250,20 +250,13 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // includes in the IDE so we can be watching for changes again. var commandLine = _commandLineStorageHandle == null ? ImmutableArray.Empty - : ReadLines(_temporaryStorageService, _commandLineStorageHandle.Identifier); + : EnumerateLines(_temporaryStorageService, _commandLineStorageHandle.Identifier).ToImmutableArray(); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); UpdateProjectOptions_NoLock(); } - static ImmutableArray ReadLines( - ITemporaryStorageServiceInternal temporaryStorageService, - TemporaryStorageIdentifier storageIdentifier) - { - return EnumerateLines(temporaryStorageService, storageIdentifier).ToImmutableArray(); - } - static IEnumerable EnumerateLines( ITemporaryStorageServiceInternal temporaryStorageService, TemporaryStorageIdentifier storageIdentifier) From c9542befb89fa830f1e63cdded821b7da4ed3146 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:24:43 -0700 Subject: [PATCH 0721/1047] simplify --- ...CompilationState.SkeletonReferenceCache.cs | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index ae82c81236ce3..d3424a78b21a9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -242,55 +242,46 @@ public readonly SkeletonReferenceCache Clone() using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - TemporaryStorageHandle? handle = null; - EmitResult emitResult; - using (var stream = SerializableBytes.CreateWritableStream()) - { - // First, emit the data to an in-memory stream. - emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + // First, emit the data to an in-memory stream. + using var stream = SerializableBytes.CreateWritableStream(); + var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - if (emitResult.Success) - { - // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the - // assembly-metadata point directly to that pointer in memory, instead of it having to make its - // own copy it needs to own the lifetime of. - stream.Position = 0; - handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - } - } - - if (handle != null) + if (emitResult.Success) { logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + stream.Position = 0; + var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); + // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. return AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); } - else + + if (logger != null) { - if (logger != null) - { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); - foreach (var diagnostic in emitResult.Diagnostics) - { - logger.Log(" " + diagnostic.GetMessage()); - } + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); } + } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; - } + return null; } } finally From b90ac79ccb502b9c5473c8c421a85f365b10b7e3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:26:30 -0700 Subject: [PATCH 0722/1047] simplify --- .../SolutionCompilationState.SkeletonReferenceCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index d3424a78b21a9..966a904322b92 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -234,7 +234,6 @@ public readonly SkeletonReferenceCache Clone() cancellationToken.ThrowIfCancellationRequested(); var logger = services.GetService(); - var temporaryStorageService = services.GetRequiredService(); try { @@ -250,6 +249,8 @@ public readonly SkeletonReferenceCache Clone() { logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + var temporaryStorageService = services.GetRequiredService(); + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the // assembly-metadata point directly to that pointer in memory, instead of it having to make its // own copy it needs to own the lifetime of. From 94a1ff5d56d95de7ccd83af8282e4ec0009dbc19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:33:16 -0700 Subject: [PATCH 0723/1047] simplify --- .../SerializerService_Reference.cs | 13 ++++--------- .../ITemporaryStorageService.cs | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f63af77354508..dc5d76476b132 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -321,12 +321,10 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT writer.WriteInt32((int)MetadataImageKind.Assembly); writer.WriteInt32(storageIdentifiers.Count); - foreach (var (name, offset, size) in storageIdentifiers) + foreach (var identifier in storageIdentifiers) { writer.WriteInt32((int)MetadataImageKind.Module); - writer.WriteString(name); - writer.WriteInt64(offset); - writer.WriteInt64(size); + identifier.WriteTo(writer); } return true; @@ -429,17 +427,14 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - length = reader.ReadInt64(); - storageIdentifier = new TemporaryStorageIdentifier(name, offset, length); + storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); } // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(length == storageStream.Length); + Contract.ThrowIfFalse(storageIdentifier.Size == storageStream.Length); return (storageIdentifier, storageStream); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 2ac827f2263e2..213e791f91d55 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.Serialization; using System.Threading; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -84,4 +85,18 @@ public void Dispose() internal sealed record TemporaryStorageIdentifier( [property: DataMember(Order = 0)] string Name, [property: DataMember(Order = 1)] long Offset, - [property: DataMember(Order = 2)] long Size); + [property: DataMember(Order = 2)] long Size) +{ + public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + } +} From 9994a4d175cde45153d5710998cabd4c0cfe090d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:36:26 -0700 Subject: [PATCH 0724/1047] Inlinem ethod' --- .../SerializerService_Reference.cs | 58 ++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index dc5d76476b132..22ecd14440a44 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -374,38 +374,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT { cancellationToken.ThrowIfCancellationRequested(); - // Get the storage identifier for the module metadata. - var (storageIdentifier, storageStream) = GetTemporaryStorageData(reader, kind, cancellationToken); - - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); - return (metadata, storageIdentifier); - } - } - - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - - // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. - var pinnedObject = new PinnedObject(array); - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); - } - - private (TemporaryStorageIdentifier identifier, UnmanagedMemoryStream storageStream) GetTemporaryStorageData( - ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) - { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); long length; @@ -435,7 +403,31 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial // sent us the full contents. var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(storageIdentifier.Size == storageStream.Length); - return (storageIdentifier, storageStream); + + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + return (metadata, storageIdentifier); + } + } + + private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) + { + Contract.ThrowIfFalse(SerializationKinds.Bits == kind); + + var array = reader.ReadByteArray(); + + // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. + var pinnedObject = new PinnedObject(array); + + // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in + // as the onDispose callback of the metadata. + return ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) From 3dabbaed90ac3575a1ebb134679b4aa9b6398393 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:37:24 -0700 Subject: [PATCH 0725/1047] extract types --- .../ITemporaryStorageService.cs | 48 ------------------- .../TemporaryStorageHandle.cs | 30 ++++++++++++ .../TemporaryStorageIdentifier.cs | 32 +++++++++++++ 3 files changed, 62 insertions(+), 48 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 213e791f91d55..26efdb3f3417c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -4,9 +4,7 @@ using System; using System.IO; -using System.Runtime.Serialization; using System.Threading; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -54,49 +52,3 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } - -/// -/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is -/// not disposed, the data should remain in storage and can be readable from any process using the information provided -/// in . Use to write -/// the data to temporary storage and get a handle to it. Use to read the data back in any process. -/// -internal sealed class TemporaryStorageHandle(IDisposable underlyingData, TemporaryStorageIdentifier identifier) : IDisposable -{ - private IDisposable? _underlyingData = underlyingData; - private TemporaryStorageIdentifier? _identifier = identifier; - - public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); - - public void Dispose() - { - var data = Interlocked.Exchange(ref _underlyingData, null); - data?.Dispose(); - _identifier = null; - } -} - -/// -/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be -/// used to identify that segment across processes, allowing for efficient sharing of data. -/// -[DataContract] -internal sealed record TemporaryStorageIdentifier( - [property: DataMember(Order = 0)] string Name, - [property: DataMember(Order = 1)] long Offset, - [property: DataMember(Order = 2)] long Size) -{ - public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) - => new( - reader.ReadRequiredString(), - reader.ReadInt64(), - reader.ReadInt64()); - - public void WriteTo(ObjectWriter writer) - { - writer.WriteString(Name); - writer.WriteInt64(Offset); - writer.WriteInt64(Size); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs new file mode 100644 index 0000000000000..11b2f4976cdb7 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is +/// not disposed, the data should remain in storage and can be readable from any process using the information provided +/// in . Use to write +/// the data to temporary storage and get a handle to it. Use to read the data back in any process. +/// +internal sealed class TemporaryStorageHandle(IDisposable underlyingData, TemporaryStorageIdentifier identifier) : IDisposable +{ + private IDisposable? _underlyingData = underlyingData; + private TemporaryStorageIdentifier? _identifier = identifier; + + public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + + public void Dispose() + { + var data = Interlocked.Exchange(ref _underlyingData, null); + data?.Dispose(); + _identifier = null; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs new file mode 100644 index 0000000000000..23b84ece0e996 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be +/// used to identify that segment across processes, allowing for efficient sharing of data. +/// +[DataContract] +internal sealed record TemporaryStorageIdentifier( + [property: DataMember(Order = 0)] string Name, + [property: DataMember(Order = 1)] long Offset, + [property: DataMember(Order = 2)] long Size) +{ + public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + } +} From 9efebbafe2061730d1e968f3e49e8d2264f5ae93 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:40:08 -0700 Subject: [PATCH 0726/1047] revert --- .../Portable/Serialization/SerializerService_Reference.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 22ecd14440a44..7f10c1f1e0902 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -62,16 +62,16 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { - if (reference is PortableExecutableReference peReference) + if (reference is PortableExecutableReference portable) { - if (peReference is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && + if (portable is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - peReference, storageIdentifiers, writer, cancellationToken)) + portable, storageIdentifiers, writer, cancellationToken)) { return; } - WritePortableExecutableReferenceTo(peReference, writer, cancellationToken); + WritePortableExecutableReferenceTo(portable, writer, cancellationToken); return; } From 5551c51a211c0c15afed1a1290b5751c7fb30795 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:42:17 -0700 Subject: [PATCH 0727/1047] rename --- .../Portable/Serialization/SerializerService_Reference.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 7f10c1f1e0902..25ccba1f9b3a4 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -401,8 +401,8 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == storageStream.Length); + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of @@ -410,7 +410,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT unsafe { var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); return (metadata, storageIdentifier); } } From ff94efe77ea20063a6e497d6f6ef099ca95f64ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:51:14 -0700 Subject: [PATCH 0728/1047] remove --- .../CoreTest/TestCompositionTests.cs | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 32535180dc45a..24535fa007888 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,36 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } + using static SourceGeneratorTelemetryCollectorWorkspaceServiceTests; public class TestCompositionTests { [Fact] public void FactoryReuse() { - var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestTemporaryStorageServiceFactory)); - var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestTemporaryStorageServiceFactory), typeof(TestErrorReportingService)); + var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory)); + var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory), typeof(TestErrorReportingService)); Assert.Same(composition1.ExportProviderFactory, composition2.ExportProviderFactory); } From 07c8ed9b74feaa86b67623dbae1da56a0338ccfc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 00:13:38 -0700 Subject: [PATCH 0729/1047] Fix --- .../CoreTestUtilities/Remote/TestSerializerService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index bf86ae8934251..9699b356e4d93 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -10,6 +10,7 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; @@ -21,6 +22,9 @@ namespace Microsoft.CodeAnalysis.UnitTests.Remote { +#if NETCOREAPP + [SupportedOSPlatform("windows")] +#endif internal sealed class TestSerializerService : SerializerService { private static readonly ImmutableDictionary s_wellKnownReferenceNames = ImmutableDictionary.Create(ReferenceEqualityComparer.Instance) From 3d000a7ccbf90d41a9298d087978d493b42a8718 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 00:18:12 -0700 Subject: [PATCH 0730/1047] Fix --- .../CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs index 7f6cfb699835d..e216af1473e25 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs @@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.Remote.Testing { +#pragma warning disable CA1416 // Validate platform compatibility internal sealed class InProcRemoteHostClientProvider : IRemoteHostClientProvider, IDisposable { [ExportWorkspaceServiceFactory(typeof(IRemoteHostClientProvider), ServiceLayer.Test), Shared, PartNotDiscoverable] @@ -99,4 +100,5 @@ public void Dispose() public Task TryGetRemoteHostClientAsync(CancellationToken cancellationToken) => Task.FromResult(_lazyClient.Value); } +#pragma warning restore CA1416 // Validate platform compatibility } From 7d920a1d04bc5817f14a42fd89096fd9badc9c23 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Sat, 20 Apr 2024 10:17:48 -0700 Subject: [PATCH 0731/1047] Reduce allocations due to multiple levels of ImmutableArray created during find references invocations. (#73118) Reduce allocations due to multiple levels of ImmutableArray created during find references invocations. This is very similar to this PR (https://github.com/dotnet/roslyn/pull/73093) that did the same thing for ImmutableArray in this context. --- .../DelegateInvokeMethodReferenceFinder.cs | 27 ++- .../FindReferencesSearchEngine.cs | 17 +- ...sSearchEngine_FindReferencesInDocuments.cs | 11 +- .../AbstractMemberScopedReferenceFinder.cs | 21 +- ...dOrPropertyOrEventSymbolReferenceFinder.cs | 2 - .../Finders/AbstractReferenceFinder.cs | 181 +++++++++--------- ...tractReferenceFinder_GlobalSuppressions.cs | 18 +- ...tractTypeParameterSymbolReferenceFinder.cs | 28 +-- ...tructorInitializerSymbolReferenceFinder.cs | 8 +- .../ConstructorSymbolReferenceFinder.cs | 83 ++++---- .../DestructorSymbolReferenceFinder.cs | 7 +- .../Finders/EventSymbolReferenceFinder.cs | 6 +- ...ExplicitConversionSymbolReferenceFinder.cs | 11 +- .../ExplicitInterfaceMethodReferenceFinder.cs | 7 +- .../Finders/FieldSymbolReferenceFinder.cs | 13 +- .../Finders/IReferenceFinder.cs | 4 +- .../Finders/NamedTypeSymbolReferenceFinder.cs | 69 ++++--- .../Finders/NamespaceSymbolReferenceFinder.cs | 46 +++-- .../Finders/OperatorSymbolReferenceFinder.cs | 14 +- .../Finders/OrdinaryMethodReferenceFinder.cs | 34 ++-- .../Finders/ParameterSymbolReferenceFinder.cs | 6 +- .../PropertyAccessorSymbolReferenceFinder.cs | 33 ++-- .../Finders/PropertySymbolReferenceFinder.cs | 67 ++++--- .../FindReferences/StandardCallbacks.cs | 18 ++ 24 files changed, 411 insertions(+), 320 deletions(-) create mode 100644 src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs diff --git a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index 8b1bd852308cb..fab481046098c 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -76,9 +76,11 @@ protected override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -89,7 +91,12 @@ protected override async ValueTask> FindReference var root = state.Root; var nodes = root.DescendantNodes(); - using var _ = ArrayBuilder.GetInstance(out var convertedAnonymousFunctions); + var invocations = nodes.Where(syntaxFacts.IsInvocationExpression) + .Where(e => state.SemanticModel.GetSymbolInfo(e, cancellationToken).Symbol?.OriginalDefinition == methodSymbol); + + foreach (var node in invocations) + processResult(CreateFinderLocation(node, state, cancellationToken), processResultData); + foreach (var node in nodes) { if (!syntaxFacts.IsAnonymousFunctionExpression(node)) @@ -103,14 +110,17 @@ protected override async ValueTask> FindReference } if (convertedType == methodSymbol.ContainingType) - convertedAnonymousFunctions.Add(node); + { + var finderLocation = CreateFinderLocation(node, state, cancellationToken); + processResult(finderLocation, processResultData); + } } - var invocations = nodes.Where(syntaxFacts.IsInvocationExpression) - .Where(e => state.SemanticModel.GetSymbolInfo(e, cancellationToken).Symbol?.OriginalDefinition == methodSymbol); + return; - return invocations.Concat(convertedAnonymousFunctions).SelectAsArray( - node => new FinderLocation( + static FinderLocation CreateFinderLocation(SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) + { + return new FinderLocation( node, new ReferenceLocation( state.Document, @@ -119,6 +129,7 @@ protected override async ValueTask> FindReference isImplicit: false, GetSymbolUsageInfo(node, state, cancellationToken), GetAdditionalFindUsagesProperties(node, state), - CandidateReason.None))); + CandidateReason.None)); + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 25f61c8deda53..eb3a7e932959e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -203,7 +203,7 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray { await finder.DetermineDocumentsToSearchAsync( symbol, globalAliases, project, _documents, - static (doc, documents) => documents.Add(doc), + StandardCallbacks.AddToHashSet, foundDocuments, _options, cancellationToken).ConfigureAwait(false); @@ -272,12 +272,15 @@ private async Task ProcessDocumentAsync( // just grab those once here and hold onto them for the lifetime of this call. var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); + // scratch array to place results in. Populated/inspected/cleared in inner loop. + using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); + foreach (var symbol in symbols) { var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState(cache, globalAliases); - await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); + await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); } } finally @@ -286,7 +289,7 @@ private async Task ProcessDocumentAsync( } async Task ProcessDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state, ArrayBuilder foundReferenceLocations) { cancellationToken.ThrowIfCancellationRequested(); @@ -297,10 +300,12 @@ async Task ProcessDocumentAsync( var group = _symbolToGroup[symbol]; foreach (var finder in _finders) { - var references = await finder.FindReferencesInDocumentAsync( - symbol, state, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) + await finder.FindReferencesInDocumentAsync( + symbol, state, StandardCallbacks.AddToArrayBuilder, foundReferenceLocations, _options, cancellationToken).ConfigureAwait(false); + foreach (var (_, location) in foundReferenceLocations) await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + + foundReferenceLocations.Clear(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 0e982257b278e..cb31339e8022c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -101,16 +101,21 @@ async ValueTask PerformSearchInDocumentAsync( async ValueTask PerformSearchInDocumentWorkerAsync( ISymbol symbol, FindReferencesDocumentState state) { + // Scratch buffer to place references for each finder. Cleared at the end of every loop iteration. + using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); + // Always perform a normal search, looking for direct references to exactly that symbol. foreach (var finder in _finders) { - var references = await finder.FindReferencesInDocumentAsync( - symbol, state, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) + await finder.FindReferencesInDocumentAsync( + symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken).ConfigureAwait(false); + foreach (var (_, location) in referencesForFinder) { var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); } + + referencesForFinder.Clear(); } // Now, for symbols that could involve inheritance, look for references to the same named entity, and diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 5e70cfac3b188..574d7b6652cd9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -49,23 +49,24 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( TSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var container = GetContainer(symbol); if (container != null) - return await FindReferencesInContainerAsync(symbol, container, state, cancellationToken).ConfigureAwait(false); - - if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) + { + await FindReferencesInContainerAsync(symbol, container, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } + else if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) { var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - return await FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken).ConfigureAwait(false); + await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - - return []; } private static ISymbol? GetContainer(ISymbol symbol) @@ -96,10 +97,12 @@ protected sealed override async ValueTask> FindRe return null; } - private ValueTask> FindReferencesInContainerAsync( + private ValueTask FindReferencesInContainerAsync( TSymbol symbol, ISymbol container, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var service = state.Document.GetRequiredLanguageService(); @@ -118,6 +121,6 @@ private ValueTask> FindReferencesInContainerAsync } } - return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 094c5db7de444..e9944e7ff30c0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -4,10 +4,8 @@ using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index c2a586f94d1e3..39b19e605551a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -24,9 +24,6 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder public const string ContainingTypeInfoPropertyName = "ContainingTypeInfo"; public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo"; - protected static readonly Action> StandardHashSetAddCallback = - static (doc, set) => set.Add(doc); - public abstract Task> DetermineGlobalAliasesAsync( ISymbol symbol, Project project, CancellationToken cancellationToken); @@ -36,8 +33,8 @@ public abstract ValueTask> DetermineCascadedSymbolsAsync public abstract Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); + public abstract ValueTask FindReferencesInDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); private static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( ISymbol symbol, FindReferencesDocumentState state, SyntaxToken token, CancellationToken cancellationToken) @@ -166,29 +163,32 @@ protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string n => syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name); [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask> FindReferencesInDocumentUsingIdentifierAsync( + protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync( ISymbol symbol, string identifier, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = await FindMatchingIdentifierTokensAsync(state, identifier, cancellationToken).ConfigureAwait(false); - return await FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken).ConfigureAwait(false); + await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } public static ValueTask> FindMatchingIdentifierTokensAsync(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) => state.Cache.FindMatchingIdentifierTokensAsync(state.Document, identifier, cancellationToken); - protected static async ValueTask> FindReferencesInTokensAsync( + protected static async ValueTask FindReferencesInTokensAsync( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray tokens, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (tokens.IsEmpty) - return []; + return; - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var token in tokens) { cancellationToken.ThrowIfCancellationRequested(); @@ -199,11 +199,9 @@ protected static async ValueTask> FindReferencesI { var finderLocation = CreateFinderLocation(state, token, reason, cancellationToken); - locations.Add(finderLocation); + processResult(finderLocation, processResultData); } } - - return locations.ToImmutableAndClear(); } protected static FinderLocation CreateFinderLocation(FindReferencesDocumentState state, SyntaxToken token, CandidateReason reason, CancellationToken cancellationToken) @@ -245,27 +243,29 @@ public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentSt return null; } - protected static async Task> FindLocalAliasReferencesAsync( + protected static async Task FindLocalAliasReferencesAsync( ArrayBuilder initialReferences, ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); - return aliasSymbols.IsDefaultOrEmpty - ? [] - : await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, cancellationToken).ConfigureAwait(false); + if (!aliasSymbols.IsDefaultOrEmpty) + await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected static async Task> FindLocalAliasReferencesAsync( + protected static async Task FindLocalAliasReferencesAsync( ArrayBuilder initialReferences, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); - return aliasSymbols.IsDefaultOrEmpty - ? [] - : await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, cancellationToken).ConfigureAwait(false); + if (!aliasSymbols.IsDefaultOrEmpty) + await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static ImmutableArray GetLocalAliasSymbols( @@ -284,53 +284,49 @@ private static ImmutableArray GetLocalAliasSymbols( return aliasSymbols.ToImmutableAndClear(); } - private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( + private static async Task FindReferencesThroughLocalAliasSymbolsAsync( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray localAliasSymbols, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var localAliasSymbol in localAliasSymbols) { - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - symbol, localAliasSymbol.Name, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(localAliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - symbol, simpleName, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + symbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - - return allAliasReferences.ToImmutableAndClear(); } - private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( + private static async Task FindReferencesThroughLocalAliasSymbolsAsync( FindReferencesDocumentState state, ImmutableArray localAliasSymbols, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var aliasSymbol in localAliasSymbols) { - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, aliasSymbol.Name, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, simpleName, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - - return allAliasReferences.ToImmutableAndClear(); } protected static Task FindDocumentsWithPredicateAsync( @@ -372,45 +368,43 @@ protected static Task FindDocumentsWithForEachStatementsAsync(Project pro /// /// If the `node` implicitly matches the `symbol`, then it will be added to `locations`. /// - protected delegate void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations); + protected delegate void CollectMatchingReferences( + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData); - protected static async Task> FindReferencesInDocumentAsync( + protected static async Task FindReferencesInDocumentAsync( FindReferencesDocumentState state, Func isRelevantDocument, - CollectMatchingReferences collectMatchingReferences, + CollectMatchingReferences collectMatchingReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var document = state.Document; var syntaxTreeInfo = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); if (isRelevantDocument(syntaxTreeInfo)) { - using var _ = ArrayBuilder.GetInstance(out var locations); - foreach (var node in state.Root.DescendantNodesAndSelf()) { cancellationToken.ThrowIfCancellationRequested(); - collectMatchingReferences(node, state, locations); + collectMatchingReferences(node, state, processResult, processResultData); } - - return locations.ToImmutableAndClear(); } - - return []; } - protected Task> FindReferencesInForEachStatementsAsync( + protected Task FindReferencesInForEachStatementsAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var info = state.SemanticFacts.GetForEachSymbols(state.SemanticModel, node); @@ -422,30 +416,33 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location: location, isImplicit: true, symbolUsageInfo, GetAdditionalFindUsagesProperties(node, state), - candidateReason: CandidateReason.None))); + candidateReason: CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInCollectionInitializerAsync( + protected Task FindReferencesInCollectionInitializerAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsCollectionInitializer; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { if (!state.SyntaxFacts.IsObjectCollectionInitializer(node)) return; @@ -460,31 +457,34 @@ void CollectMatchingReferences( var location = expression.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(expression, state, cancellationToken); - locations.Add(new FinderLocation(expression, new ReferenceLocation( + var result = new FinderLocation(expression, new ReferenceLocation( state.Document, alias: null, location: location, isImplicit: true, symbolUsageInfo, GetAdditionalFindUsagesProperties(expression, state), - candidateReason: CandidateReason.None))); + candidateReason: CandidateReason.None)); + processResult(result, processResultData); } } } } - protected Task> FindReferencesInDeconstructionAsync( + protected Task FindReferencesInDeconstructionAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var semanticModel = state.SemanticModel; var semanticFacts = state.SemanticFacts; @@ -500,25 +500,28 @@ void CollectMatchingReferences( var location = state.SyntaxFacts.GetDeconstructionReferenceLocation(node); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInAwaitExpressionAsync( + protected Task FindReferencesInAwaitExpressionAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var awaitExpressionMethod = state.SemanticFacts.GetGetAwaiterMethod(state.SemanticModel, node); @@ -527,25 +530,28 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInImplicitObjectCreationExpressionAsync( + protected Task FindReferencesInImplicitObjectCreationExpressionAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { // Avoid binding unrelated nodes if (!state.SyntaxFacts.IsImplicitObjectCreationExpression(node)) @@ -558,9 +564,10 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } @@ -841,8 +848,10 @@ protected abstract Task DetermineDocumentsToSearchAsync( Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - protected abstract ValueTask> FindReferencesInDocumentAsync( - TSymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); + protected abstract ValueTask FindReferencesInDocumentAsync( + TSymbol symbol, FindReferencesDocumentState state, + Action processResult, TData processResultData, + FindReferencesSearchOptions options, CancellationToken cancellationToken); protected virtual Task> DetermineGlobalAliasesAsync( TSymbol symbol, Project project, CancellationToken cancellationToken) @@ -869,12 +878,12 @@ public sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - public sealed override ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken) + public sealed override ValueTask FindReferencesInDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? FindReferencesInDocumentAsync(typedSymbol, state, options, cancellationToken) - : new ValueTask>([]); + ? FindReferencesInDocumentAsync(typedSymbol, state, processResult, processResultData, options, cancellationToken) + : ValueTaskFactory.CompletedTask; } public sealed override ValueTask> DetermineCascadedSymbolsAsync( @@ -896,11 +905,11 @@ protected virtual ValueTask> DetermineCascadedSymbolsAsy return new([]); } - protected static ValueTask> FindReferencesInDocumentUsingSymbolNameAsync( - TSymbol symbol, FindReferencesDocumentState state, CancellationToken cancellationToken) + protected static ValueTask FindReferencesInDocumentUsingSymbolNameAsync( + TSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindReferencesInDocumentUsingIdentifierAsync( - symbol, symbol.Name, state, cancellationToken); + symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } protected static async Task> GetAllMatchingGlobalAliasNamesAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs index d3c9275698895..13e1de1396ca0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -51,28 +50,30 @@ static bool SupportsGlobalSuppression(ISymbol symbol) /// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")] /// [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask> FindReferencesInDocumentInsideGlobalSuppressionsAsync( + protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppressionsAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (!ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId)) - return []; + return; // Check if we have any relevant global attributes in this document. var info = await SyntaxTreeIndex.GetRequiredIndexAsync(state.Document, cancellationToken).ConfigureAwait(false); if (!info.ContainsGlobalSuppressMessageAttribute) - return []; + return; var semanticModel = state.SemanticModel; var suppressMessageAttribute = semanticModel.Compilation.SuppressMessageAttributeType(); if (suppressMessageAttribute == null) - return []; + return; // Check if we have any instances of the symbol documentation comment ID string literals within global attributes. // These string literals represent references to the symbol. if (!TryGetExpectedDocumentationCommentId(docCommentId, out var expectedDocCommentId)) - return []; + return; var syntaxFacts = state.SyntaxFacts; @@ -80,17 +81,16 @@ protected static async ValueTask> FindReferencesI // perform semantic checks to ensure these are valid references to the symbol // and if so, add these locations to the computed references. var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var token in root.DescendantTokens()) { if (IsCandidate(state, token, expectedDocCommentId.Span, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken)) { var referenceLocation = CreateReferenceLocation(offsetOfReferenceInToken, token, root, state.Document, syntaxFacts); - locations.Add(new FinderLocation(token.GetRequiredParent(), referenceLocation)); + processResult(new FinderLocation(token.GetRequiredParent(), referenceLocation), processResultData); } } - return locations.ToImmutableAndClear(); + return; // Local functions static bool IsCandidate( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index 756ce8036d1fa..38cc211cfb9ad 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -7,16 +7,17 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; internal abstract class AbstractTypeParameterSymbolReferenceFinder : AbstractReferenceFinder { - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( ITypeParameterSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -30,15 +31,19 @@ protected sealed override async ValueTask> FindRe var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - var normalReferences = await FindReferencesInTokensAsync( + await FindReferencesInTokensAsync( symbol, state, tokens.WhereAsArray(static (token, state) => !IsObjectCreationToken(token, state), state), + processResult, + processResultData, cancellationToken).ConfigureAwait(false); - var objectCreationReferences = GetObjectCreationReferences( - tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state)); + GetObjectCreationReferences( + tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state), + processResult, + processResultData); - return normalReferences.Concat(objectCreationReferences); + return; static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState state) { @@ -47,19 +52,18 @@ static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState syntaxFacts.IsObjectCreationExpression(token.Parent.Parent); } - ImmutableArray GetObjectCreationReferences(ImmutableArray objectCreationTokens) + void GetObjectCreationReferences( + ImmutableArray objectCreationTokens, + Action processResult, + TData processResultData) { - using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var token in objectCreationTokens) { Contract.ThrowIfNull(token.Parent?.Parent); var typeInfo = state.SemanticModel.GetTypeInfo(token.Parent.Parent, cancellationToken); if (symbol.Equals(typeInfo.Type, SymbolEqualityComparer.Default)) - result.Add(CreateFinderLocation(state, token, CandidateReason.None, cancellationToken)); + processResult(CreateFinderLocation(state, token, CandidateReason.None, cancellationToken), processResultData); } - - return result.ToImmutableAndClear(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index c79b50715238d..4258a0eca666b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -51,9 +51,11 @@ protected override Task DetermineDocumentsToSearchAsync( }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -68,7 +70,9 @@ protected sealed override async ValueTask> FindRe static (token, tuple) => TokensMatch(tuple.state, token, tuple.methodSymbol.ContainingType.Name, tuple.cancellationToken), (state, methodSymbol, cancellationToken)); - return await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, cancellationToken).ConfigureAwait(false); + await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + + return; // local functions static bool TokensMatch( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 8fea80d2ffb77..5037d55e9d1ad 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -9,8 +9,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -90,18 +90,18 @@ private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxF => syntaxFacts.TryGetPredefinedType(token, out var actualType) && predefinedType == actualType; - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(out var result); - // First just look for this normal constructor references using the name of it's containing type. var name = methodSymbol.ContainingType.Name; await AddReferencesInDocumentWorkerAsync( - methodSymbol, name, state, result, cancellationToken).ConfigureAwait(false); + methodSymbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); // Next, look for constructor references through a global alias to our containing type. foreach (var globalAlias in state.GlobalAliases) @@ -113,67 +113,61 @@ await AddReferencesInDocumentWorkerAsync( continue; await AddReferencesInDocumentWorkerAsync( - methodSymbol, globalAlias, state, result, cancellationToken).ConfigureAwait(false); + methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - // Nest, our containing type might itself have local aliases to it in this particular file. - // If so, see what the local aliases are and then search for constructor references to that. - using var _2 = ArrayBuilder.GetInstance(out var typeReferences); - await NamedTypeSymbolReferenceFinder.AddReferencesToTypeOrGlobalAliasToItAsync( - methodSymbol.ContainingType, state, typeReferences, cancellationToken).ConfigureAwait(false); - - var aliasReferences = await FindLocalAliasReferencesAsync( - typeReferences, methodSymbol, state, cancellationToken).ConfigureAwait(false); - // Finally, look for constructor references to predefined types (like `new int()`), // implicit object references, and inside global suppression attributes. - result.AddRange(await FindPredefinedTypeReferencesAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindReferencesInImplicitObjectCreationExpressionAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + await FindPredefinedTypeReferencesAsync( + methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - result.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + await FindReferencesInImplicitObjectCreationExpressionAsync( + methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async Task AddReferencesInDocumentWorkerAsync( + private static async Task AddReferencesInDocumentWorkerAsync( IMethodSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - result.AddRange(await FindOrdinaryReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); - result.AddRange(await FindAttributeReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + await FindOrdinaryReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindAttributeReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static ValueTask> FindOrdinaryReferencesAsync( + private static ValueTask FindOrdinaryReferencesAsync( IMethodSymbol symbol, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindReferencesInDocumentUsingIdentifierAsync( - symbol, name, state, cancellationToken); + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindPredefinedTypeReferencesAsync( + private static ValueTask FindPredefinedTypeReferencesAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return new([]); + return ValueTaskFactory.CompletedTask; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -181,23 +175,27 @@ private static ValueTask> FindPredefinedTypeRefer static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask> FindAttributeReferencesAsync( + private static ValueTask FindAttributeReferencesAsync( IMethodSymbol symbol, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, cancellationToken) - : new([]); + ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, processResult, processResultData, cancellationToken) + : ValueTaskFactory.CompletedTask; } - private Task> FindReferencesInImplicitObjectCreationExpressionAsync( + private Task FindReferencesInImplicitObjectCreationExpressionAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // Only check `new (...)` calls that supply enough arguments to match all the required parameters for the constructor. @@ -210,13 +208,13 @@ private Task> FindReferencesInImplicitObjectCreat ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var syntaxFacts = state.SyntaxFacts; if (!syntaxFacts.IsImplicitObjectCreationExpression(node)) @@ -237,9 +235,10 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs index 245f390365b46..a6c1623d5ba95 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -28,12 +29,14 @@ protected override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return new ValueTask>([]); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 76a169a867822..8784ccd1b37ef 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -48,12 +48,14 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IEventSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, cancellationToken); + return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index b4e00a0482da6..4078ddcbba83d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; @@ -48,8 +47,8 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( Contract.ThrowIfNull(underlyingNamedType); using var _ = PooledHashSet.GetInstance(out var result); - await FindDocumentsAsync(project, documents, StandardHashSetAddCallback, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); - await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardHashSetAddCallback, result, cancellationToken).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, StandardCallbacks.AddToHashSet, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardCallbacks.AddToHashSet, result, cancellationToken).ConfigureAwait(false); // Ignore any documents that don't also have an explicit cast in them. foreach (var document in result) @@ -60,9 +59,11 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( } } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -72,7 +73,7 @@ protected sealed override ValueTask> FindReferenc static (token, state) => IsPotentialReference(state.SyntaxFacts, token), state); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index b048e24c1a03f..a92e4f0ca1eba 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -29,13 +30,15 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // An explicit method can't be referenced anywhere. - return new([]); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index d3657581f7cfd..11c23a50ec558 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -40,16 +40,17 @@ protected override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IFieldSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - return nameReferences.Concat(suppressionReferences); + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 8e4738b2a4728..f5b717542dc20 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -63,9 +63,11 @@ Task DetermineDocumentsToSearchAsync( /// /// Implementations of this method must be thread-safe. /// - ValueTask> FindReferencesInDocumentAsync( + ValueTask FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index ff8f1084f9b13..35816840c7d6c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -104,9 +105,11 @@ private static bool IsPotentialReference( predefinedType == actualType; } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -115,31 +118,34 @@ protected override async ValueTask> FindReference // First find all references to this type, either with it's actual name, or through potential // global alises to it. await AddReferencesToTypeOrGlobalAliasToItAsync( - namedType, state, initialReferences, cancellationToken).ConfigureAwait(false); + namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + + // The items in initialReferences need to be both reported and used later to calculate additional results. + foreach (var location in initialReferences) + processResult(location, processResultData); // This named type may end up being locally aliased as well. If so, now find all the references // to the local alias. - initialReferences.AddRange(await FindLocalAliasReferencesAsync( - initialReferences, state, cancellationToken).ConfigureAwait(false)); + await FindLocalAliasReferencesAsync( + initialReferences, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - initialReferences.AddRange(await FindPredefinedTypeReferencesAsync( - namedType, state, cancellationToken).ConfigureAwait(false)); + await FindPredefinedTypeReferencesAsync( + namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - namedType, state, cancellationToken).ConfigureAwait(false)); - - return initialReferences.ToImmutableAndClear(); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( + internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, - ArrayBuilder nonAliasReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { await AddNonAliasReferencesAsync( - namedType, namedType.Name, state, nonAliasReferences, cancellationToken).ConfigureAwait(false); + namedType, namedType.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); foreach (var globalAlias in state.GlobalAliases) { @@ -150,7 +156,7 @@ await AddNonAliasReferencesAsync( continue; await AddNonAliasReferencesAsync( - namedType, globalAlias, state, nonAliasReferences, cancellationToken).ConfigureAwait(false); + namedType, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } @@ -159,24 +165,27 @@ await AddNonAliasReferencesAsync( /// only if it referenced though (which might be the actual name /// of the type, or a global alias to it). /// - private static async ValueTask AddNonAliasReferencesAsync( + private static async ValueTask AddNonAliasReferencesAsync( INamedTypeSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder nonAliasesReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - nonAliasesReferences.AddRange(await FindOrdinaryReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + await FindOrdinaryReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - nonAliasesReferences.AddRange(await FindAttributeReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + await FindAttributeReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static ValueTask> FindOrdinaryReferencesAsync( + private static ValueTask FindOrdinaryReferencesAsync( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // Get the parent node that best matches what this token represents. For example, if we have `new a.b()` @@ -185,17 +194,19 @@ private static ValueTask> FindOrdinaryReferencesA // associate with the type, but rather with the constructor itself. return FindReferencesInDocumentUsingIdentifierAsync( - namedType, name, state, cancellationToken); + namedType, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindPredefinedTypeReferencesAsync( + private static ValueTask FindPredefinedTypeReferencesAsync( INamedTypeSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var predefinedType = symbol.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return new([]); + return ValueTaskFactory.CompletedTask; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -203,17 +214,19 @@ private static ValueTask> FindPredefinedTypeRefer static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask> FindAttributeReferencesAsync( + private static ValueTask FindAttributeReferencesAsync( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix) - ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, cancellationToken) - : new([]); + ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken) + : ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index bb03240ba2ce4..e08a522c68576 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -50,24 +50,26 @@ await FindDocumentsAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var initialReferences); - if (symbol.IsGlobalNamespace) { await AddGlobalNamespaceReferencesAsync( - symbol, state, initialReferences, cancellationToken).ConfigureAwait(false); + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } else { + using var _ = ArrayBuilder.GetInstance(out var initialReferences); + var namespaceName = symbol.Name; await AddNamedReferencesAsync( - symbol, namespaceName, state, initialReferences, cancellationToken).ConfigureAwait(false); + symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); foreach (var globalAlias in state.GlobalAliases) { @@ -78,41 +80,45 @@ await AddNamedReferencesAsync( continue; await AddNamedReferencesAsync( - symbol, globalAlias, state, initialReferences, cancellationToken).ConfigureAwait(false); + symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); } - initialReferences.AddRange(await FindLocalAliasReferencesAsync( - initialReferences, symbol, state, cancellationToken).ConfigureAwait(false)); + // The items in initialReferences need to be both reported and used later to calculate additional results. + foreach (var location in initialReferences) + processResult(location, processResultData); - initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false)); - } + await FindLocalAliasReferencesAsync( + initialReferences, symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return initialReferences.ToImmutableAndClear(); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async ValueTask AddNamedReferencesAsync( + private static async ValueTask AddNamedReferencesAsync( INamespaceSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder initialReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = await FindMatchingIdentifierTokensAsync( state, name, cancellationToken).ConfigureAwait(false); - initialReferences.AddRange(await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false)); + await FindReferencesInTokensAsync( + symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static async Task AddGlobalNamespaceReferencesAsync( + private static async Task AddGlobalNamespaceReferencesAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, - ArrayBuilder initialReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = state.Root @@ -121,7 +127,7 @@ private static async Task AddGlobalNamespaceReferencesAsync( static (token, state) => state.SyntaxFacts.IsGlobalNamespaceKeyword(token), state); - initialReferences.AddRange(await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false)); + await FindReferencesInTokensAsync( + symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index bd1d4210e5476..5b3b92945dc16 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -47,9 +47,11 @@ private static Task FindDocumentsAsync( project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -60,12 +62,10 @@ protected sealed override async ValueTask> FindRe static (token, tuple) => IsPotentialReference(tuple.state.SyntaxFacts, tuple.op, token), (state, op)); - var opReferences = await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - return opReferences.Concat(suppressionReferences); + await FindReferencesInTokensAsync( + symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 7ace214c57e6e..1131b5489010d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -108,34 +108,30 @@ private static bool IsGetAwaiterMethod(IMethodSymbol methodSymbol) private static bool IsAddMethod(IMethodSymbol methodSymbol) => methodSymbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName; - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameMatches = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - var forEachMatches = IsForEachMethod(symbol) - ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var deconstructMatches = IsDeconstructMethod(symbol) - ? await FindReferencesInDeconstructionAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachMethod(symbol)) + await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var getAwaiterMatches = IsGetAwaiterMethod(symbol) - ? await FindReferencesInAwaitExpressionAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsDeconstructMethod(symbol)) + await FindReferencesInDeconstructionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + if (IsGetAwaiterMethod(symbol)) + await FindReferencesInAwaitExpressionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var addMatches = IsAddMethod(symbol) - ? await FindReferencesInCollectionInitializerAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return nameMatches.Concat(forEachMatches, deconstructMatches, getAwaiterMatches, suppressionReferences, addMatches); + if (IsAddMethod(symbol)) + await FindReferencesInCollectionInitializerAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index d7231cad5721a..b13c380c6f9c9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -38,13 +38,15 @@ protected override Task DetermineDocumentsToSearchAsync( return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name); } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IParameterSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, cancellationToken); + return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } protected override async ValueTask> DetermineCascadedSymbolsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index a15f25ab2979c..7c12a31b19e68 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -60,34 +60,35 @@ await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var references = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (symbol.AssociatedSymbol is not IPropertySymbol property || !options.AssociatePropertyReferencesWithSpecificAccessor) { - return references; + return; } - var propertyReferences = await ReferenceFinders.Property.FindReferencesInDocumentAsync( - property, state, - options with { AssociatePropertyReferencesWithSpecificAccessor = false }, - cancellationToken).ConfigureAwait(false); - - var accessorReferences = propertyReferences.WhereAsArray( - loc => + await ReferenceFinders.Property.FindReferencesInDocumentAsync( + property, + state, + static (loc, data) => { var accessors = GetReferencedAccessorSymbols( - state, property, loc.Node, cancellationToken); - return accessors.Contains(symbol); - }); - - return references.Concat(accessorReferences); + data.state, data.property, loc.Node, data.cancellationToken); + if (accessors.Contains(data.symbol)) + data.processResult(loc, data.processResultData); + }, + (property, symbol, state, processResult, processResultData, cancellationToken), + options with { AssociatePropertyReferencesWithSpecificAccessor = false }, + cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 0f228294634e1..cb70c9670db68 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -118,39 +118,44 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( private static bool IsForEachProperty(IPropertySymbol symbol) => symbol.Name == WellKnownMemberNames.CurrentPropertyName; - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IPropertySymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - if (options.AssociatePropertyReferencesWithSpecificAccessor) - { - // We want to associate property references to a specific accessor (if an accessor - // is being referenced). Check if this reference would match an accessor. If so, do - // not add it. It will be added by PropertyAccessorSymbolReferenceFinder. - nameReferences = nameReferences.WhereAsArray(loc => + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, + state, + static (loc, data) => { - var accessors = GetReferencedAccessorSymbols( - state, symbol, loc.Node, cancellationToken); - return accessors.IsEmpty; - }); - } + var useResult = true; + if (data.options.AssociatePropertyReferencesWithSpecificAccessor) + { + // We want to associate property references to a specific accessor (if an accessor + // is being referenced). Check if this reference would match an accessor. If so, do + // not add it. It will be added by PropertyAccessorSymbolReferenceFinder. + var accessors = GetReferencedAccessorSymbols( + data.state, data.symbol, loc.Node, data.cancellationToken); + useResult = accessors.IsEmpty; + } + + if (useResult) + data.processResult(loc, data.processResultData); + }, + processResultData: (self: this, symbol, state, processResult, processResultData, options, cancellationToken), + cancellationToken).ConfigureAwait(false); - var forEachReferences = IsForEachProperty(symbol) - ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachProperty(symbol)) + await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var indexerReferences = symbol.IsIndexer - ? await FindIndexerReferencesAsync(symbol, state, options, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindIndexerReferencesAsync(symbol, state, processResult, processResultData, options, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( @@ -167,9 +172,11 @@ private static Task FindDocumentWithIndexerMemberCrefAsync( project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } - private static async Task> FindIndexerReferencesAsync( + private static async Task FindIndexerReferencesAsync( IPropertySymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -177,7 +184,7 @@ private static async Task> FindIndexerReferencesA { // Looking for individual get/set references. Don't find anything here. // these results will be provided by the PropertyAccessorSymbolReferenceFinder - return []; + return; } var syntaxFacts = state.SyntaxFacts; @@ -188,7 +195,6 @@ private static async Task> FindIndexerReferencesA syntaxFacts.IsImplicitElementAccess(node) || syntaxFacts.IsConditionalAccessExpression(node) || syntaxFacts.IsIndexerMemberCref(node)); - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var node in indexerReferenceExpressions) { @@ -202,14 +208,13 @@ private static async Task> FindIndexerReferencesA var location = state.SyntaxTree.GetLocation(new TextSpan(indexerReference.SpanStart, 0)); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: false, symbolUsageInfo, GetAdditionalFindUsagesProperties(node, state), - candidateReason))); + candidateReason)); + processResult(result, processResultData); } - - return locations.ToImmutableAndClear(); } private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs new file mode 100644 index 0000000000000..5696c32d2e8b9 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.FindSymbols; + +internal static class StandardCallbacks +{ + public static readonly Action> AddToHashSet = + static (data, set) => set.Add(data); + + public static readonly Action> AddToArrayBuilder = + static (data, builder) => builder.Add(data); +} From 5134c93010c93652a0a47c1cb9a95fde4d96b58b Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Sat, 20 Apr 2024 10:58:22 -0700 Subject: [PATCH 0732/1047] Add single value cache to BloomFilter hash calculation (#73103) Add single value cache to BloomFilter hash calculation Although this isn't horribly expensive, I noticed that we typically call BloomFilter.ProbablyContains many times with the same input string. This string is then hashed a number of times (about 13 times if the string is present, less if not). Instead, we can cache what these hashes are as almost all created bloom filters will calculate the same hashes. Testing yielded around a 99% hit rate. Should have a *slight* positive effect for find references and other scenarios using the bloom filters. --- .../Test/Utilities/BloomFilterTests.cs | 110 ++++++++++++++-- .../Portable/Shared/Utilities/BloomFilter.cs | 124 +++++++++++++++--- 2 files changed, 206 insertions(+), 28 deletions(-) diff --git a/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs b/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs index d8fd33a73fc2c..33bff5542e174 100644 --- a/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs +++ b/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs @@ -41,7 +41,8 @@ private static string GenerateString(int value) return builder.ToString(); } - private static void Test(bool isCaseSensitive) + [Theory, CombinatorialData] + public void Test(bool isCaseSensitive) { var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; var strings = new HashSet(GenerateStrings(2000).Skip(500).Take(1000), comparer); @@ -79,14 +80,6 @@ private static void Test(bool isCaseSensitive) } } - [Fact] - public void Test1() - => Test(isCaseSensitive: true); - - [Fact] - public void TestInsensitive() - => Test(isCaseSensitive: false); - [Fact] public void TestEmpty() { @@ -106,6 +99,33 @@ public void TestEmpty() } } + [Fact] + public void TestCacheWhenEmpty() + { + BloomFilter.BloomFilterHash.ResetCachedEntry(); + + _ = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: false, []); + + Assert.False(BloomFilter.BloomFilterHash.TryGetCachedEntry(out _, out _)); + } + + [Fact] + public void TestCacheAfterCalls() + { + var filter1 = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: false, []); + var filter2 = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: true, []); + + _ = filter1.ProbablyContains("test1"); + Assert.True(BloomFilter.BloomFilterHash.TryGetCachedEntry(out var isCaseSensitive, out var value)); + Assert.True(!isCaseSensitive); + Assert.Equal("test1", value); + + _ = filter2.ProbablyContains("test2"); + Assert.True(BloomFilter.BloomFilterHash.TryGetCachedEntry(out isCaseSensitive, out value)); + Assert.True(isCaseSensitive); + Assert.Equal("test2", value); + } + [Fact] public void TestSerialization() { @@ -180,6 +200,78 @@ public void TestInt64() } } + [Theory, CombinatorialData] + public void TestCacheCorrectness(bool isCaseSensitive, bool reverse) + { + var allStringsToTest = GenerateStrings(100_000); + + var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + var allHashSets = new List> + { + new HashSet(GenerateStrings(1_000), comparer), + new HashSet(GenerateStrings(1_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(1_000).Where((s, i) => i % 1 == 1), comparer), + new HashSet(GenerateStrings(10_000), comparer), + new HashSet(GenerateStrings(10_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(10_000).Where((s, i) => i % 1 == 1), comparer), + new HashSet(GenerateStrings(100_000), comparer), + new HashSet(GenerateStrings(100_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(100_000).Where((s, i) => i % 1 == 1), comparer), + }; + + // Try the patterns where we're searching smaller filters then larger ones. Then the pattern of larger ones then smaller ones. + if (reverse) + allHashSets.Reverse(); + + // Try several different probability levels to ensure we maintain the correct false positive rate. We + // must always preserve the true 0 negative rate. + for (var d = 0.1; d >= 0.0001; d /= 10) + { + // Get a bloom filter for each set of strings. + var allFilters = allHashSets.Select(s => new BloomFilter(d, isCaseSensitive, s)).ToArray(); + + // The double array stores the correct/incorrect count per run. + var allCounts = allHashSets.Select(_ => new double[2]).ToArray(); + + // We want to take each string, and test it against each bloom filter. This will ensure that the caches + // we have when computing against one bloom filter don't infect the results of the other bloom filters. + foreach (var test in allStringsToTest) + { + for (var i = 0; i < allHashSets.Count; i++) + { + var strings = allHashSets[i]; + var filter = allFilters[i]; + var counts = allCounts[i]; + var actualContains = strings.Contains(test); + var filterContains = filter.ProbablyContains(test); + + // if the filter says no, then it can't be in the real set. + if (!filterContains) + Assert.False(actualContains); + + if (actualContains == filterContains) + { + counts[0]++; + } + else + { + counts[1]++; + } + } + } + + // Now validate for this set of bloom filters, and this particular probability level, that all the + // rates remain correct for each bloom filter. + foreach (var counts in allCounts) + { + var correctCount = counts[0]; + var incorrectCount = counts[1]; + var falsePositivePercentage = incorrectCount / (correctCount + incorrectCount); + Assert.True(falsePositivePercentage < (d * 1.5), string.Format("falsePositivePercentage={0}, d={1}", falsePositivePercentage, d)); + } + } + } + private static HashSet CreateLongs(List ints) { var result = new HashSet(); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs b/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs index 6cc6cad8d4b1a..4d740090dc8a4 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; namespace Microsoft.CodeAnalysis.Shared.Utilities; @@ -113,7 +114,7 @@ private static int ComputeK(int expectedCount, double falsePositiveProbability) /// /// Murmur hash is public domain. Actual code is included below as reference. /// - private int ComputeHash(string key, int seed) + private static int ComputeHash(string key, int seed, bool isCaseSensitive) { unchecked { @@ -127,8 +128,8 @@ private int ComputeHash(string key, int seed) var index = 0; while (numberOfCharsLeft >= 2) { - var c1 = GetCharacter(key, index); - var c2 = GetCharacter(key, index + 1); + var c1 = GetCharacter(key, index, isCaseSensitive); + var c2 = GetCharacter(key, index + 1, isCaseSensitive); h = CombineTwoCharacters(h, c1, c2); @@ -140,7 +141,7 @@ private int ComputeHash(string key, int seed) // odd length. if (numberOfCharsLeft == 1) { - var c = GetCharacter(key, index); + var c = GetCharacter(key, index, isCaseSensitive); h = CombineLastCharacter(h, c); } @@ -225,10 +226,10 @@ private static uint CombineTwoCharacters(uint h, uint c1, uint c2) } } - private char GetCharacter(string key, int index) + private static char GetCharacter(string key, int index, bool isCaseSensitive) { var c = key[index]; - return _isCaseSensitive ? c : char.ToLowerInvariant(c); + return isCaseSensitive ? c : char.ToLowerInvariant(c); } private static char GetCharacter(long key, int index) @@ -319,13 +320,13 @@ public void Add(string value) { for (var i = 0; i < _hashFunctionCount; i++) { - _bitArray[GetBitArrayIndex(value, i)] = true; + var hash = ComputeHash(value, i, _isCaseSensitive); + _bitArray[GetBitArrayIndexFromHash(hash)] = true; } } - private int GetBitArrayIndex(string value, int i) + private int GetBitArrayIndexFromHash(int hash) { - var hash = ComputeHash(value, i); hash %= _bitArray.Length; return Math.Abs(hash); } @@ -334,22 +335,24 @@ public void Add(long value) { for (var i = 0; i < _hashFunctionCount; i++) { - _bitArray[GetBitArrayIndex(value, i)] = true; + var hash = ComputeHash(value, i); + _bitArray[GetBitArrayIndexFromHash(hash)] = true; } } - private int GetBitArrayIndex(long value, int i) - { - var hash = ComputeHash(value, i); - hash %= _bitArray.Length; - return Math.Abs(hash); - } - public bool ProbablyContains(string value) { + // Request an array of immutable hashes for this input. Note that it's possible + // that the returned array might return a cached entry calculated by a different + // bloom filter and thus might have more entries than we need, but it's ok as + // it's guaranteed that the first _hashFunctionCount of those values are the values + // we would have computed had we not used the cache. + var hashes = BloomFilterHash.GetOrCreateHashArray(value, _isCaseSensitive, _hashFunctionCount); + for (var i = 0; i < _hashFunctionCount; i++) { - if (!_bitArray[GetBitArrayIndex(value, i)]) + var hash = hashes[i]; + if (!_bitArray[GetBitArrayIndexFromHash(hash)]) { return false; } @@ -362,7 +365,8 @@ public bool ProbablyContains(long value) { for (var i = 0; i < _hashFunctionCount; i++) { - if (!_bitArray[GetBitArrayIndex(value, i)]) + var hash = ComputeHash(value, i); + if (!_bitArray[GetBitArrayIndexFromHash(hash)]) { return false; } @@ -395,4 +399,86 @@ private static bool IsEquivalent(BitArray array1, BitArray array2) return true; } + + /// + /// Provides mechanism to efficiently obtain bloom filter hash for a value. Backed by a single element cache. + /// + internal sealed class BloomFilterHash + { + private static BloomFilterHash? s_cachedHash; + + private readonly string _value; + private readonly bool _isCaseSensitive; + private readonly ImmutableArray _hashes; + + private BloomFilterHash(string value, bool isCaseSensitive, int hashFunctionCount) + { + _value = value; + _isCaseSensitive = isCaseSensitive; + + var hashBuilder = new FixedSizeArrayBuilder(hashFunctionCount); + + for (var i = 0; i < hashFunctionCount; i++) + hashBuilder.Add(BloomFilter.ComputeHash(value, i, _isCaseSensitive)); + + _hashes = hashBuilder.MoveToImmutable(); + } + + /// + /// Although calculating this hash isn't terribly expensive, it does involve multiple + /// (usually around 13) hashings of the string (the actual count is ). + /// The typical usage pattern of bloom filters is that some operation (eg: find references) + /// requires asking a multitude of bloom filters whether a particular value is likely contained. + /// The vast majority of those bloom filters will end up hashing that string to the same values, so + /// we put those values into a simple cache and see if it can be used before calculating. + /// Local testing has put the hit rate of this at around 99%. + /// + /// Note that it's possible for this method to return an array from the cache longer than hashFunctionCount, + /// but if so, it's guaranteed that the values returned in the first hashFunctionCount entries are + /// the same as if the cache hadn't been used. + /// + public static ImmutableArray GetOrCreateHashArray(string value, bool isCaseSensitive, int hashFunctionCount) + { + var cachedHash = s_cachedHash; + + // Not an equivalency check on the hashFunctionCount as a longer array is ok. This is because the + // values in the array are determined by value and isCaseSensitive and hashFunctionCount is simply + // used to determine the length of the returned array. As long as the cached entry matches the value + // and isCaseSensitive and is at least as long as we need, then we can use it. + if (cachedHash == null + || cachedHash._isCaseSensitive != isCaseSensitive + || cachedHash._hashes.Length < hashFunctionCount + || cachedHash._value != value) + { + cachedHash = new BloomFilterHash(value, isCaseSensitive, hashFunctionCount); + s_cachedHash = cachedHash; + } + + return cachedHash._hashes; + } + + // Used only by tests + internal static bool TryGetCachedEntry(out bool isCaseSensitive, out string value) + { + var cachedHash = s_cachedHash; + + if (cachedHash == null) + { + isCaseSensitive = false; + value = string.Empty; + + return false; + } + + isCaseSensitive = cachedHash._isCaseSensitive; + value = cachedHash._value; + + return true; + } + + internal static void ResetCachedEntry() + { + s_cachedHash = null; + } + } } From 43c9becc3914aa8e44fcbc374a9e279c2ec3b962 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:22:22 -0700 Subject: [PATCH 0733/1047] remove unused method --- .../Serialization/SerializerService_Reference.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 25ccba1f9b3a4..f45d6fce6e9e0 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -415,21 +415,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } } - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - - // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. - var pinnedObject = new PinnedObject(array); - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); - } - private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); From 7fd00210d3e0980ed8c29eda2dabbc71e9654e57 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:22:47 -0700 Subject: [PATCH 0734/1047] remove pinned object concept --- .../SerializerService_Reference.cs | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f45d6fce6e9e0..f93bdbb67646e 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -459,35 +459,6 @@ private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference referen } } - private sealed class PinnedObject : IDisposable - { - // shouldn't be read-only since GCHandle is a mutable struct - private GCHandle _gcHandle; - - public PinnedObject(byte[] array) - => _gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - - internal IntPtr GetPointer() - => _gcHandle.AddrOfPinnedObject(); - - private void OnDispose() - { - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - } - - ~PinnedObject() - => OnDispose(); - - public void Dispose() - { - GC.SuppressFinalize(this); - OnDispose(); - } - } - private sealed class MissingMetadataReference : PortableExecutableReference { private readonly DocumentationProvider _provider; From 30cb1ae32b4e7262ef46152869cb227e9282f352 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:24:19 -0700 Subject: [PATCH 0735/1047] duplicate to make tracking down issue easier --- .../SerializerService_Reference.cs | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f93bdbb67646e..82101f4abec48 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -390,28 +390,44 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); storageIdentifier = storageHandle.Identifier; + + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); + + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + return (metadata, storageIdentifier); + } } else { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - } - // Now read in the module data using that identifier. This will either be reading from the host's memory if - // they passed us the information about that memory segment. Or it will be reading from our own memory if they - // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - return (metadata, storageIdentifier); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + return (metadata, storageIdentifier); + } } } From efab42e9130ae12bc38703882d25fe949df3822d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:27:10 -0700 Subject: [PATCH 0736/1047] Break into helper methods --- .../SerializerService_Reference.cs | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 82101f4abec48..21344c00bc1e2 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -376,43 +376,37 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); - long length; - TemporaryStorageIdentifier storageIdentifier; - if (kind == SerializationKinds.Bits) + return kind == SerializationKinds.Bits + ? ReadModuleMetadataFromBits() + : ReadModuleMetadataFromMemoryMappedFile(); + + (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromMemoryMappedFile() + { + // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it + // will not be released by the host. + var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); + return ReadModuleMetadataFromStorage(storageIdentifier); + } + + (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromBits() { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the // server side so that we can refer to this data uniformly. using var stream = SerializableBytes.CreateWritableStream(); CopyByteArrayToStream(reader, stream, cancellationToken); - length = stream.Length; + var length = stream.Length; stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - storageIdentifier = storageHandle.Identifier; + var storageIdentifier = storageHandle.Identifier; - // Now read in the module data using that identifier. This will either be reading from the host's memory if - // they passed us the information about that memory segment. Or it will be reading from our own memory if they - // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); - - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - return (metadata, storageIdentifier); - } + return ReadModuleMetadataFromStorage(storageIdentifier); } - else - { - // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it - // will not be released by the host. - storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); + (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromStorage( + TemporaryStorageIdentifier storageIdentifier) + { // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. From 1a9005cc5eaf355f742f6dbc9b3616ba13e57482 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 12:53:48 -0700 Subject: [PATCH 0737/1047] Tweak --- .../Portable/Serialization/SerializerService_Reference.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 21344c00bc1e2..748cdcc70ca08 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -413,13 +413,10 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. unsafe { var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length); return (metadata, storageIdentifier); } } From 066476e9756986dcc72ffa8e5833cd6a14f8e3e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 14:25:11 -0700 Subject: [PATCH 0738/1047] rename file' --- .../TemporaryStorageService.cs | 456 ++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs new file mode 100644 index 0000000000000..b3fd55389ce4c --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -0,0 +1,456 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Temporarily stores text and streams in memory mapped files. +/// +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif +internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal +{ + /// + /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage + /// units. + /// + /// + /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to + /// the next lower power-of-2 size for Roslyn.sln itself. + /// + /// + private const long SingleFileThreshold = 256 * 1024; + + /// + /// The size in bytes of a memory mapped file created to store multiple temporary objects. + /// + /// + /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of + /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but + /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. + /// + /// + private const long MultiFileBlockSize = SingleFileThreshold * 32; + + private readonly IWorkspaceThreadingService? _workspaceThreadingService; + private readonly ITextFactoryService _textFactory; + + /// + /// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks + /// of each field). + /// + /// + /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is + /// available in source control history. The use of exclusive locks was not causing any measurable + /// performance overhead even on 28-thread machines at the time this was written. + /// + private readonly object _gate = new(); + + /// + /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer + /// allocation until space is no longer available in it. Access should be synchronized on + /// + private ReferenceCountedDisposable.WeakReference _weakFileReference; + + /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on + /// + /// + private string? _name; + + /// The total size of the current memory mapped file for multiple storage units. Access should be + /// synchronized on + /// + private long _fileSize; + + /// + /// The offset into the current memory mapped file where the next storage unit can be held. Access should be + /// synchronized on . + /// + /// + private long _offset; + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingService, ITextFactoryService textFactory) + { + _workspaceThreadingService = workspaceThreadingService; + _textFactory = textFactory; + } + + public ITemporaryTextStorageInternal CreateTemporaryTextStorage() + => new TemporaryTextStorage(this); + + public TemporaryTextStorage AttachTemporaryTextStorage( + string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) + => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this); + storage.WriteStream(stream, cancellationToken); + var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); + return new(storage, identifier); + } + + Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); + return storage.ReadStream(cancellationToken); + } + + internal TemporaryStorageHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + { + var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); + return new(storage, storageIdentifier); + } + + /// + /// Allocate shared storage of a specified size. + /// + /// + /// "Small" requests are fulfilled from oversized memory mapped files which support several individual + /// storage units. Larger requests are allocated in their own memory mapped files. + /// + /// The size of the shared storage block to allocate. + /// A describing the allocated block. + private MemoryMappedInfo CreateTemporaryStorage(long size) + { + if (size >= SingleFileThreshold) + { + // Larger blocks are allocated separately + var mapName = CreateUniqueName(size); + var storage = MemoryMappedFile.CreateNew(mapName, size); + return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); + } + + lock (_gate) + { + // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted + // handle to a memory mapped file is obtained in this section, it must either be disposed before + // returning or returned to the caller who will own it through the MemoryMappedInfo. + var reference = _weakFileReference.TryAddReference(); + if (reference == null || _offset + size > _fileSize) + { + var mapName = CreateUniqueName(MultiFileBlockSize); + var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); + + reference = new ReferenceCountedDisposable(file); + _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); + _name = mapName; + _fileSize = MultiFileBlockSize; + _offset = size; + return new MemoryMappedInfo(reference, _name, offset: 0, size: size); + } + else + { + // Reserve additional space in the existing storage location + Contract.ThrowIfNull(_name); + _offset += size; + return new MemoryMappedInfo(reference, _name, _offset - size, size); + } + } + } + + public static string CreateUniqueName(long size) + => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); + + public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName + { + private readonly TemporaryStorageService _service; + private SourceHashAlgorithm _checksumAlgorithm; + private Encoding? _encoding; + private ImmutableArray _contentHash; + private MemoryMappedInfo? _memoryMappedInfo; + + public TemporaryTextStorage(TemporaryStorageService service) + => _service = service; + + public TemporaryTextStorage( + TemporaryStorageService service, + string storageName, + long offset, + long size, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) + { + _service = service; + _checksumAlgorithm = checksumAlgorithm; + _encoding = encoding; + _contentHash = contentHash; + _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + } + + // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 + // Offset, Size not accessed if Name is null + public string? Name => _memoryMappedInfo?.Name; + public long Offset => _memoryMappedInfo!.Offset; + public long Size => _memoryMappedInfo!.Size; + + /// + /// Gets the value for the property for the + /// represented by this temporary storage. + /// + public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; + + /// + /// Gets the value for the property for the + /// represented by this temporary storage. + /// + public Encoding? Encoding => _encoding; + + /// + /// Gets the checksum for the represented by this temporary storage. This is equivalent + /// to calling . + /// + public ImmutableArray ContentHash => _contentHash; + + public void Dispose() + { + // Destructors of SafeHandle and FileStream in MemoryMappedFile + // will eventually release resources if this Dispose is not called + // explicitly + _memoryMappedInfo?.Dispose(); + + _memoryMappedInfo = null; + _encoding = null; + _contentHash = default; + } + + public SourceText ReadText(CancellationToken cancellationToken) + { + if (_memoryMappedInfo == null) + { + throw new InvalidOperationException(); + } + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) + { + using var stream = _memoryMappedInfo.CreateReadableStream(); + using var reader = CreateTextReaderFromTemporaryStorage(stream); + + // we pass in encoding we got from original source text even if it is null. + return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); + } + } + + public async Task ReadTextAsync(CancellationToken cancellationToken) + { + // There is a reason for implementing it like this: proper async implementation + // that reads the underlying memory mapped file stream in an asynchronous fashion + // doesn't actually work. Windows doesn't offer + // any non-blocking way to read from a memory mapped file; the underlying memcpy + // may block as the memory pages back in and that's something you have to live + // with. Therefore, any implementation that attempts to use async will still + // always be blocking at least one threadpool thread in the memcpy in the case + // of a page fault. Therefore, if we're going to be blocking a thread, we should + // just block one thread and do the whole thing at once vs. a fake "async" + // implementation which will continue to requeue work back to the thread pool. + if (_service._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return ReadText(cancellationToken); + } + + public void WriteText(SourceText text, CancellationToken cancellationToken) + { + if (_memoryMappedInfo != null) + { + throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); + } + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) + { + _checksumAlgorithm = text.ChecksumAlgorithm; + _encoding = text.Encoding; + _contentHash = text.GetContentHash(); + + // the method we use to get text out of SourceText uses Unicode (2bytes per char). + var size = Encoding.Unicode.GetMaxByteCount(text.Length); + _memoryMappedInfo = _service.CreateTemporaryStorage(size); + + // Write the source text out as Unicode. We expect that to be cheap. + using var stream = _memoryMappedInfo.CreateWritableStream(); + using var writer = new StreamWriter(stream, Encoding.Unicode); + + text.Write(writer, cancellationToken); + } + } + + public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) + { + // See commentary in ReadTextAsync for why this is implemented this way. + if (_service._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + WriteText(text, cancellationToken); + } + + private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + { + var src = (char*)stream.PositionPointer; + + // BOM: Unicode, little endian + // Skip the BOM when creating the reader + Debug.Assert(*src == 0xFEFF); + + return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); + } + } + + internal sealed class TemporaryStreamStorage : IDisposable + { + private readonly TemporaryStorageService _service; + private MemoryMappedInfo? _memoryMappedInfo; + + public TemporaryStreamStorage(TemporaryStorageService service) + => _service = service; + + public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) + { + _service = service; + _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + } + + public string Name => _memoryMappedInfo!.Name; + public long Offset => _memoryMappedInfo!.Offset; + public long Size => _memoryMappedInfo!.Size; + + public void Dispose() + { + // Destructors of SafeHandle and FileStream in MemoryMappedFile + // will eventually release resources if this Dispose is not called + // explicitly + _memoryMappedInfo?.Dispose(); + _memoryMappedInfo = null; + } + + public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) + { + if (_memoryMappedInfo == null) + { + throw new InvalidOperationException(); + } + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _memoryMappedInfo.CreateReadableStream(); + } + } + + public void WriteStream(Stream stream, CancellationToken cancellationToken) + { + if (_memoryMappedInfo != null) + throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) + { + var size = stream.Length; + _memoryMappedInfo = _service.CreateTemporaryStorage(size); + using var viewStream = _memoryMappedInfo.CreateWritableStream(); + + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); + } + } + } + } +} + +internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength +{ + private char* _position; + private readonly char* _end; + + public DirectMemoryAccessStreamReader(char* src, int length) + : base(length) + { + RoslynDebug.Assert(src != null); + RoslynDebug.Assert(length >= 0); + + _position = src; + _end = _position + length; + } + + public override int Peek() + { + if (_position >= _end) + { + return -1; + } + + return *_position; + } + + public override int Read() + { + if (_position >= _end) + { + return -1; + } + + return *_position++; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0 || index >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || (index + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + count = Math.Min(count, (int)(_end - _position)); + if (count > 0) + { + Marshal.Copy((IntPtr)_position, buffer, index, count); + _position += count; + } + + return count; + } +} From 0b866e5bc9902c5c40ac98ac00331dcd5be0e687 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 14:26:22 -0700 Subject: [PATCH 0739/1047] Handles' --- .../VisualStudioMetadataReference.Snapshot.cs | 4 +- .../VisualStudioMetadataReferenceManager.cs | 29 +- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 70 +-- .../TemporaryStorageServiceFactory.cs | 450 ------------------ ...CompilationState.SkeletonReferenceCache.cs | 3 +- 6 files changed, 55 insertions(+), 503 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 711aa80dcd26e..8c407e68825d5 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageIdentifiers - => _provider.GetStorageIdentifiers(this.FilePath, _timestamp.Value); + public IReadOnlyList StorageHandles + => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 637aa33983542..0ef1f97083953 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -43,7 +43,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the /// host to send the entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorageIdentifiers = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -86,14 +86,14 @@ public void Dispose() } } - public IReadOnlyList? GetStorageIdentifiers(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorageIdentifiers.TryGetValue(source, out var storages)) + s_metadataToStorageHandles.TryGetValue(source, out var handler)) { - return storages; + return handler; } return null; @@ -154,17 +154,17 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); + using var _ = ArrayBuilder.GetInstance(out var storageHandles); var newMetadata = CreateAssemblyMetadata(key, key => { // // - GetMetadataFromTemporaryStorage(key, out var storageIdentifier, out var metadata); - storageIdentifiers.Add(storageIdentifier); + GetMetadataFromTemporaryStorage(key, out var storageHandle, out var metadata); + storageHandles.Add(storageHandle); return metadata; }); - s_metadataToStorageIdentifiers.Add(newMetadata, storageIdentifiers.ToImmutable()); + s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); return newMetadata; } @@ -172,9 +172,9 @@ AssemblyMetadata GetMetadataWorker() } private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out ModuleMetadata metadata) + FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out ModuleMetadata metadata) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageIdentifier, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageHandle, out var stream); unsafe { @@ -185,7 +185,7 @@ private void GetMetadataFromTemporaryStorage( return; void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out UnmanagedMemoryStream stream) { int size; @@ -215,13 +215,12 @@ void GetStorageInfoFromTemporaryStorage( // location, so we can create a metadata value wrapping that. This will also let us share the memory // for that metadata value with our OOP process. copyStream.Position = 0; - var handle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); - storageIdentifier = handle.Identifier; + storageHandle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); } // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. - stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); - + stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageHandle.Identifier, CancellationToken.None); + GC.KeepAlive(storageHandle); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index 2eaee88847d46..ffb6efafc2523 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageIdentifiers { get; } + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 748cdcc70ca08..3376cf0c4d145 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -64,9 +64,9 @@ public virtual void WriteMetadataReferenceTo(MetadataReference reference, Object { if (reference is PortableExecutableReference portable) { - if (portable is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && + if (portable is ISupportTemporaryStorage { StorageHandles: { Count: > 0 } handles } && TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - portable, storageIdentifiers, writer, cancellationToken)) + portable, handles, writer, cancellationToken)) { return; } @@ -232,7 +232,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe var filePath = reader.ReadString(); - if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageIdentifiers)) + if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageHandles)) { // TODO: deal with xml document provider properly // should we shadow copy xml doc comment? @@ -252,7 +252,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe _documentationService.GetDocumentationProvider(filePath) : XmlDocumentationProvider.Default; return new SerializedMetadataReference( - properties, filePath, metadata, storageIdentifiers, documentProvider); + properties, filePath, metadata, storageHandles, documentProvider); } private static void WriteTo(MetadataReferenceProperties properties, ObjectWriter writer, CancellationToken cancellationToken) @@ -310,27 +310,27 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( PortableExecutableReference reference, - IReadOnlyList storageIdentifiers, + IReadOnlyList handles, ObjectWriter writer, CancellationToken cancellationToken) { - Contract.ThrowIfTrue(storageIdentifiers.Count == 0); + Contract.ThrowIfTrue(handles.Count == 0); WritePortableExecutableReferenceHeaderTo(reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); - writer.WriteInt32(storageIdentifiers.Count); + writer.WriteInt32(handles.Count); - foreach (var identifier in storageIdentifiers) + foreach (var handle in handles) { writer.WriteInt32((int)MetadataImageKind.Module); - identifier.WriteTo(writer); + handle.Identifier.WriteTo(writer); } return true; } - private (Metadata metadata, ImmutableArray storageIdentifiers)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -344,7 +344,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); - using var pooledStorageIdentifiers = Creator.CreateList(); + using var pooledHandles = Creator.CreateList(); var count = reader.ReadInt32(); for (var i = 0; i < count; i++) @@ -352,24 +352,24 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT metadataKind = (MetadataImageKind)reader.ReadInt32(); Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - var (metadata, storageIdentifier) = ReadModuleMetadataFrom(reader, kind, cancellationToken); + var (metadata, storageHandle) = ReadModuleMetadataFrom(reader, kind, cancellationToken); pooledMetadata.Object.Add(metadata); - pooledStorageIdentifiers.Object.Add(storageIdentifier); + pooledHandles.Object.Add(storageHandle); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorageIdentifiers.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(pooledMetadata.Object), pooledHandles.Object.ToImmutableArrayOrEmpty()); } else { Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); + return (moduleInfo.metadata, [moduleInfo.storageHandle]); } } - private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -380,15 +380,16 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT ? ReadModuleMetadataFromBits() : ReadModuleMetadataFromMemoryMappedFile(); - (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromMemoryMappedFile() + (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - return ReadModuleMetadataFromStorage(storageIdentifier); + var storageHandle = _storageService.GetHandle(storageIdentifier); + return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromBits() + (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromBits() { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the // server side so that we can refer to this data uniformly. @@ -399,25 +400,26 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - var storageIdentifier = storageHandle.Identifier; - - return ReadModuleMetadataFromStorage(storageIdentifier); + return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromStorage( - TemporaryStorageIdentifier storageIdentifier) + (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromStorage( + TemporaryStorageHandle storageHandle) { // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageHandle.Identifier, cancellationToken); + Contract.ThrowIfFalse(storageHandle.Identifier.Size == unmanagedStream.Length); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. unsafe { var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length); - return (metadata, storageIdentifier); + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + return (metadata, storageHandle); } } } @@ -503,22 +505,22 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storageIdentifiers; + private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; - public IReadOnlyList StorageIdentifiers => _storageIdentifiers; + public IReadOnlyList StorageHandles => _storageHandles; public SerializedMetadataReference( MetadataReferenceProperties properties, string? fullPath, Metadata metadata, - ImmutableArray storagesIdentifiers, + ImmutableArray storageHandles, DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { - Contract.ThrowIfTrue(storagesIdentifiers.IsDefault); + Contract.ThrowIfTrue(storageHandles.IsDefault); _metadata = metadata; - _storageIdentifiers = storagesIdentifiers; + _storageHandles = storageHandles; _provider = initialDocumentation; } @@ -533,6 +535,6 @@ protected override Metadata GetMetadataImpl() => _metadata; protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new SerializedMetadataReference(properties, FilePath, _metadata, _storageIdentifiers, _provider); + => new SerializedMetadataReference(properties, FilePath, _metadata, _storageHandles, _provider); } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs deleted file mode 100644 index 365a2870ab1cb..0000000000000 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,450 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// Temporarily stores text and streams in memory mapped files. -/// -#if NETCOREAPP -[SupportedOSPlatform("windows")] -#endif -internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal -{ - /// - /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage - /// units. - /// - /// - /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to - /// the next lower power-of-2 size for Roslyn.sln itself. - /// - /// - private const long SingleFileThreshold = 256 * 1024; - - /// - /// The size in bytes of a memory mapped file created to store multiple temporary objects. - /// - /// - /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of - /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but - /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. - /// - /// - private const long MultiFileBlockSize = SingleFileThreshold * 32; - - private readonly IWorkspaceThreadingService? _workspaceThreadingService; - private readonly ITextFactoryService _textFactory; - - /// - /// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks - /// of each field). - /// - /// - /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is - /// available in source control history. The use of exclusive locks was not causing any measurable - /// performance overhead even on 28-thread machines at the time this was written. - /// - private readonly object _gate = new(); - - /// - /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer - /// allocation until space is no longer available in it. Access should be synchronized on - /// - private ReferenceCountedDisposable.WeakReference _weakFileReference; - - /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on - /// - /// - private string? _name; - - /// The total size of the current memory mapped file for multiple storage units. Access should be - /// synchronized on - /// - private long _fileSize; - - /// - /// The offset into the current memory mapped file where the next storage unit can be held. Access should be - /// synchronized on . - /// - /// - private long _offset; - - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingService, ITextFactoryService textFactory) - { - _workspaceThreadingService = workspaceThreadingService; - _textFactory = textFactory; - } - - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TemporaryTextStorage(this); - - public TemporaryTextStorage AttachTemporaryTextStorage( - string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) - => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - - public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage(this); - storage.WriteStream(stream, cancellationToken); - var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(storage, identifier); - } - - Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - - public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); - return storage.ReadStream(cancellationToken); - } - - /// - /// Allocate shared storage of a specified size. - /// - /// - /// "Small" requests are fulfilled from oversized memory mapped files which support several individual - /// storage units. Larger requests are allocated in their own memory mapped files. - /// - /// The size of the shared storage block to allocate. - /// A describing the allocated block. - private MemoryMappedInfo CreateTemporaryStorage(long size) - { - if (size >= SingleFileThreshold) - { - // Larger blocks are allocated separately - var mapName = CreateUniqueName(size); - var storage = MemoryMappedFile.CreateNew(mapName, size); - return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); - } - - lock (_gate) - { - // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted - // handle to a memory mapped file is obtained in this section, it must either be disposed before - // returning or returned to the caller who will own it through the MemoryMappedInfo. - var reference = _weakFileReference.TryAddReference(); - if (reference == null || _offset + size > _fileSize) - { - var mapName = CreateUniqueName(MultiFileBlockSize); - var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); - - reference = new ReferenceCountedDisposable(file); - _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); - _name = mapName; - _fileSize = MultiFileBlockSize; - _offset = size; - return new MemoryMappedInfo(reference, _name, offset: 0, size: size); - } - else - { - // Reserve additional space in the existing storage location - Contract.ThrowIfNull(_name); - _offset += size; - return new MemoryMappedInfo(reference, _name, _offset - size, size); - } - } - } - - public static string CreateUniqueName(long size) - => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName - { - private readonly TemporaryStorageService _service; - private SourceHashAlgorithm _checksumAlgorithm; - private Encoding? _encoding; - private ImmutableArray _contentHash; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryTextStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryTextStorage( - TemporaryStorageService service, - string storageName, - long offset, - long size, - SourceHashAlgorithm checksumAlgorithm, - Encoding? encoding, - ImmutableArray contentHash) - { - _service = service; - _checksumAlgorithm = checksumAlgorithm; - _encoding = encoding; - _contentHash = contentHash; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); - } - - // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size not accessed if Name is null - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public Encoding? Encoding => _encoding; - - /// - /// Gets the checksum for the represented by this temporary storage. This is equivalent - /// to calling . - /// - public ImmutableArray ContentHash => _contentHash; - - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - - _memoryMappedInfo = null; - _encoding = null; - _contentHash = default; - } - - public SourceText ReadText(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) - { - using var stream = _memoryMappedInfo.CreateReadableStream(); - using var reader = CreateTextReaderFromTemporaryStorage(stream); - - // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); - } - } - - public async Task ReadTextAsync(CancellationToken cancellationToken) - { - // There is a reason for implementing it like this: proper async implementation - // that reads the underlying memory mapped file stream in an asynchronous fashion - // doesn't actually work. Windows doesn't offer - // any non-blocking way to read from a memory mapped file; the underlying memcpy - // may block as the memory pages back in and that's something you have to live - // with. Therefore, any implementation that attempts to use async will still - // always be blocking at least one threadpool thread in the memcpy in the case - // of a page fault. Therefore, if we're going to be blocking a thread, we should - // just block one thread and do the whole thing at once vs. a fake "async" - // implementation which will continue to requeue work back to the thread pool. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - return ReadText(cancellationToken); - } - - public void WriteText(SourceText text, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) - { - _checksumAlgorithm = text.ChecksumAlgorithm; - _encoding = text.Encoding; - _contentHash = text.GetContentHash(); - - // the method we use to get text out of SourceText uses Unicode (2bytes per char). - var size = Encoding.Unicode.GetMaxByteCount(text.Length); - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - - // Write the source text out as Unicode. We expect that to be cheap. - using var stream = _memoryMappedInfo.CreateWritableStream(); - using var writer = new StreamWriter(stream, Encoding.Unicode); - - text.Write(writer, cancellationToken); - } - } - - public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) - { - // See commentary in ReadTextAsync for why this is implemented this way. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - WriteText(text, cancellationToken); - } - - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) - { - var src = (char*)stream.PositionPointer; - - // BOM: Unicode, little endian - // Skip the BOM when creating the reader - Debug.Assert(*src == 0xFEFF); - - return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); - } - } - - internal sealed class TemporaryStreamStorage : IDisposable - { - private readonly TemporaryStorageService _service; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryStreamStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) - { - _service = service; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); - } - - public string Name => _memoryMappedInfo!.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - _memoryMappedInfo = null; - } - - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _memoryMappedInfo.CreateReadableStream(); - } - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) - { - var size = stream.Length; - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - using var viewStream = _memoryMappedInfo.CreateWritableStream(); - - using var pooledObject = SharedPools.ByteArray.GetPooledObject(); - var buffer = pooledObject.Object; - while (true) - { - var count = stream.Read(buffer, 0, buffer.Length); - if (count == 0) - break; - - viewStream.Write(buffer, 0, count); - } - } - } - } -} - -internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength -{ - private char* _position; - private readonly char* _end; - - public DirectMemoryAccessStreamReader(char* src, int length) - : base(length) - { - RoslynDebug.Assert(src != null); - RoslynDebug.Assert(length >= 0); - - _position = src; - _end = _position + length; - } - - public override int Peek() - { - if (_position >= _end) - { - return -1; - } - - return *_position; - } - - public override int Read() - { - if (_position >= _end) - { - return -1; - } - - return *_position++; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0 || index >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0 || (index + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - count = Math.Min(count, (int)(_end - _position)); - if (count > 0) - { - Marshal.Copy((IntPtr)_position, buffer, index, count); - _position += count; - } - - return count; - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 966a904322b92..ce8f883f0e12a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -259,8 +259,9 @@ public readonly SkeletonReferenceCache Clone() // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. - return AssemblyMetadata.CreateFromStream( + var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); + GC.KeepAlive(handle); } if (logger != null) From 4b76b6a9b3790747ddfe759c3a29d652bd48679a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 15:21:41 -0700 Subject: [PATCH 0740/1047] fix --- .../Solution/SolutionCompilationState.SkeletonReferenceCache.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index ce8f883f0e12a..dafd5f5e383c7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -262,6 +262,7 @@ public readonly SkeletonReferenceCache Clone() var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); GC.KeepAlive(handle); + return result; } if (logger != null) From 4aebf85ac3946085ea364fcc3240f33b393feb24 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 16:32:00 -0700 Subject: [PATCH 0741/1047] Rename --- .../VisualStudioMetadataReferenceManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 0ef1f97083953..ffc07f78fd511 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -91,9 +91,9 @@ public void Dispose() var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorageHandles.TryGetValue(source, out var handler)) + s_metadataToStorageHandles.TryGetValue(source, out var handles)) { - return handler; + return handles; } return null; From f96e1e72f990621853654cffa104782cf24e15d2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 00:24:14 -0700 Subject: [PATCH 0742/1047] Run real memory-mapped-file code during tests --- .../Serialization/SerializerService.cs | 9 +- .../SerializerService_Reference.cs | 116 ++---------------- .../TestTemporaryStorageServiceFactory.cs | 24 ---- .../SolutionTests/SolutionTestHelpers.cs | 8 +- .../CoreTest/SyntaxReferenceTests.cs | 6 +- .../CoreTest/TestCompositionTests.cs | 8 +- 6 files changed, 23 insertions(+), 148 deletions(-) delete mode 100644 src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 02868db1077ea..1c6dd05f655e3 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -6,16 +6,19 @@ using System.Collections.Concurrent; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif internal partial class SerializerService : ISerializerService { [ExportWorkspaceServiceFactory(typeof(ISerializerService), layer: ServiceLayer.Default), Shared] @@ -36,7 +39,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly SolutionServices _workspaceServices; - private readonly ITemporaryStorageServiceInternal _storageService; + private readonly TemporaryStorageService _storageService; private readonly ITextFactoryService _textService; private readonly IDocumentationProviderService? _documentationService; private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider; @@ -48,7 +51,7 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - _storageService = workspaceServices.GetRequiredService(); + _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); _documentationService = workspaceServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index c5e19134e2309..f2a912bbf53ec 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -359,32 +359,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } var metadataKind = (MetadataImageKind)imageKind; - if (_storageService == null) - { - if (metadataKind == MetadataImageKind.Assembly) - { - using var pooledMetadata = Creator.CreateList(); - - var count = reader.ReadInt32(); - for (var i = 0; i < count; i++) - { - metadataKind = (MetadataImageKind)reader.ReadInt32(); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - pooledMetadata.Object.Add(ReadModuleMetadataFrom(reader, kind)); -#pragma warning restore CA2016 - } - - return (AssemblyMetadata.Create(pooledMetadata.Object), storages: default); - } - - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storages: default); -#pragma warning restore CA2016 - } - if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); @@ -421,25 +395,18 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var storageStream = storage.ReadStream(cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); - var metadata = GetMetadata(storageStream, length); - - return (metadata, storage); - } - - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - var pinnedObject = new PinnedObject(array); - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + return (metadata, storage); + } } - private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( + private (TemporaryStorageService.TemporaryStreamStorage storage, long length) GetTemporaryStorage( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); @@ -475,40 +442,6 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static ModuleMetadata GetMetadata(Stream stream, long length) - { - if (stream is UnmanagedMemoryStream unmanagedStream) - { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - return ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - } - } - - PinnedObject pinnedObject; - if (stream is MemoryStream memory && - memory.TryGetBuffer(out var buffer) && - buffer.Offset == 0) - { - pinnedObject = new PinnedObject(buffer.Array!); - } - else - { - var array = new byte[length]; - stream.Read(array, 0, (int)length); - pinnedObject = new PinnedObject(array); - } - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); - } - private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -553,35 +486,6 @@ private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference referen } } - private sealed class PinnedObject : IDisposable - { - // shouldn't be read-only since GCHandle is a mutable struct - private GCHandle _gcHandle; - - public PinnedObject(byte[] array) - => _gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - - internal IntPtr GetPointer() - => _gcHandle.AddrOfPinnedObject(); - - private void OnDispose() - { - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - } - - ~PinnedObject() - => OnDispose(); - - public void Dispose() - { - GC.SuppressFinalize(this); - OnDispose(); - } - } - private sealed class MissingMetadataReference : PortableExecutableReference { private readonly DocumentationProvider _provider; diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs deleted file mode 100644 index 21147f43736bd..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.UnitTests.Persistence -{ - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } -} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index 0cdc9229c893f..adab4802f8a7b 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests @@ -18,16 +17,13 @@ public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + => CreateWorkspace(); public static Workspace CreateWorkspaceWithRecoverableText() => CreateWorkspace(); public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) - => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics([typeof(TestTemporaryStorageServiceFactory)], testHost); + => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); #nullable disable diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index 2ce0a35a4bb70..be1d4ab59b970 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; using CS = Microsoft.CodeAnalysis.CSharp; @@ -26,10 +25,7 @@ private static Workspace CreateWorkspace(Type[] additionalParts = null) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + => CreateWorkspace(); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 13237a0dcb699..24535fa007888 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,21 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { + using static SourceGeneratorTelemetryCollectorWorkspaceServiceTests; + public class TestCompositionTests { [Fact] public void FactoryReuse() { - var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestTemporaryStorageServiceFactory)); - var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestTemporaryStorageServiceFactory), typeof(TestErrorReportingService)); + var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory)); + var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory), typeof(TestErrorReportingService)); Assert.Same(composition1.ExportProviderFactory, composition2.ExportProviderFactory); } From 179d0584222cb38e07a45598175d88578b33b6d2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 00:26:04 -0700 Subject: [PATCH 0743/1047] docs --- src/Workspaces/Core/Portable/Serialization/SerializerService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 1c6dd05f655e3..52125801cf770 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -51,6 +51,8 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // storage service here is well known. _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); From 1cecae23aae8fc20a769476dc68af27bd60ced25 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 00:35:18 -0700 Subject: [PATCH 0744/1047] tweak --- src/Workspaces/Core/Portable/Serialization/SerializerService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 52125801cf770..e786280b8b51c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -51,7 +51,7 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the // storage service here is well known. _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); From 3a072904e3032953d554ccecf9857b7721d7076f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 09:39:25 -0700 Subject: [PATCH 0745/1047] test warning' --- .../CoreTestUtilities/Remote/TestSerializerService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index bf86ae8934251..5d8152928777a 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -19,6 +19,8 @@ using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; +#pragma warning disable CA1416 // Validate platform compatibility + namespace Microsoft.CodeAnalysis.UnitTests.Remote { internal sealed class TestSerializerService : SerializerService From cd2fa5c7e851b88985159449286db9fa1922e069 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 09:41:58 -0700 Subject: [PATCH 0746/1047] usings --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f2a912bbf53ec..d73abc3722bb8 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -7,10 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection.Metadata; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; From a267879e773fb2f4a023583ea1b17ca5f553d97e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 09:54:37 -0700 Subject: [PATCH 0747/1047] Remove unneeded tests methods --- .../CoreTest/SolutionTests/SolutionTestHelpers.cs | 6 ------ src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index adab4802f8a7b..fa04089a158a9 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -16,12 +16,6 @@ internal static class SolutionTestHelpers public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost testHost = TestHost.InProcess) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); - public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace(); - - public static Workspace CreateWorkspaceWithRecoverableText() - => CreateWorkspace(); - public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 6f023828ec35f..e1ac87c0b8243 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2651,7 +2651,7 @@ public void TestDocumentChangedOnDiskIsNotObserved() var file = Temp.CreateFile().WriteAllText(text1, Encoding.UTF8); // create a solution that evicts from the cache immediately. - using var workspace = CreateWorkspaceWithRecoverableText(); + using var workspace = CreateWorkspace(); var sol = workspace.CurrentSolution; var pid = ProjectId.CreateNewId(); From bb311d59fcc6068cea970d78371f6366165cb00f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 10:01:00 -0700 Subject: [PATCH 0748/1047] Fixup tests --- .../CoreTest/SyntaxReferenceTests.cs | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index be1d4ab59b970..6614d2e7f9b26 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -2,30 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Linq; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; +using Microsoft.CodeAnalysis.Shared.Extensions; using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; +using System.Threading.Tasks; +using System.Threading; namespace Microsoft.CodeAnalysis.UnitTests { [UseExportProvider] [Trait(Traits.Feature, Traits.Features.Workspace)] - public class SyntaxReferenceTests : TestBase + public sealed class SyntaxReferenceTests : TestBase { - private static Workspace CreateWorkspace(Type[] additionalParts = null) - => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); - - private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace(); + private static Workspace CreateWorkspace() + => new AdhocWorkspace(FeaturesTestCompositions.Features.GetHostServices()); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { @@ -48,16 +44,16 @@ private static Solution AddSingleFileVisualBasicProject(Solution solution, strin } [Fact] - public void TestCSharpReferenceToZeroWidthNode() + public async Task TestCSharpReferenceToZeroWidthNode() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" public class C<> { } "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // this is an expected TypeParameterSyntax with a missing identifier token (it is zero-length w/ an error attached to it) var node = tree.GetRoot().DescendantNodes().OfType().Single(); @@ -71,15 +67,15 @@ public class C<> } [Fact] - public void TestVisualBasicReferenceToZeroWidthNode() + public async Task TestVisualBasicReferenceToZeroWidthNode() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" Public Class C(Of ) End Class "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // this is an expected TypeParameterSyntax with a missing identifier token (it is zero-length w/ an error attached to it) var node = tree.GetRoot().DescendantNodes().OfType().Single(); @@ -93,9 +89,9 @@ End Class } [Fact] - public void TestCSharpReferenceToNodeInStructuredTrivia() + public async Task TestCSharpReferenceToNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" #if true || true public class C @@ -103,7 +99,7 @@ public class C } #endif "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var node = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -116,9 +112,9 @@ public class C } [Fact] - public void TestVisualBasicReferenceToNodeInStructuredTrivia() + public async Task TestVisualBasicReferenceToNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" #If True Or True Then Public Class C @@ -126,7 +122,7 @@ End Class #End If "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var node = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -139,9 +135,9 @@ End Class } [Fact] - public void TestCSharpReferenceToZeroWidthNodeInStructuredTrivia() + public async Task TestCSharpReferenceToZeroWidthNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" #if true || public class C @@ -150,7 +146,7 @@ public class C #endif "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var binary = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -169,7 +165,7 @@ public class C [Fact] public async System.Threading.Tasks.Task TestVisualBasicReferenceToZeroWidthNodeInStructuredTriviaAsync() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" #If (True Or ) Then Public Class C @@ -177,7 +173,7 @@ End Class #End If "); - var tree = await solution.Projects.First().Documents.First().GetSyntaxTreeAsync(); + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var binary = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); From 00826916c936f57fadbd00c3b65cd3699652e3b4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 11:45:22 -0700 Subject: [PATCH 0749/1047] Simplify --- ...emporaryStorageService.MemoryMappedInfo.cs | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 0bd1fd88552e7..ba3b7a37ec0c6 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -178,35 +178,20 @@ public void Dispose() _memoryMappedFile.Dispose(); } - private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream : UnmanagedMemoryStream + private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream( + ReferenceCountedDisposable accessor, + long length) : UnmanagedMemoryStream( + (byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, + length) { - private readonly ReferenceCountedDisposable _accessor; - private byte* _start; - - public MemoryMappedViewUnmanagedMemoryStream(ReferenceCountedDisposable accessor, long length) - : base((byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, length) - { - _accessor = accessor; - _start = this.PositionPointer; - } + private readonly ReferenceCountedDisposable _accessor = accessor; protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (disposing) - { _accessor.Dispose(); - } - - _start = null; } - - /// - /// Get underlying native memory directly. - /// - public IntPtr GetPointer() - => (IntPtr)_start; } } } From 2bef2b967e4f9aeccd99359754171e7f65e80da4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 11:51:40 -0700 Subject: [PATCH 0750/1047] Add assert --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 6bf545a2707ff..abda6f7a06a2e 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -397,6 +397,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); + Contract.ThrowIfTrue(length != storageHandle.Identifier.Size); return ReadModuleMetadataFromStorage(storageHandle); } From d69c434f3f520b27c8f7cb8493341f5653d365e4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 11:55:58 -0700 Subject: [PATCH 0751/1047] Simplify api --- .../Portable/Serialization/SerializerService_Reference.cs | 2 -- .../Portable/TemporaryStorage/TemporaryStorageService.cs | 1 + .../Host/TemporaryStorage/ITemporaryStorageService.cs | 5 ++++- .../Host/TemporaryStorage/TrivialTemporaryStorageService.cs | 1 + .../ProjectSystem/ProjectSystemProjectOptionsProcessor.cs | 2 -- .../SolutionCompilationState.SkeletonReferenceCache.cs | 1 - .../WorkspaceServiceTests/TemporaryStorageServiceTests.cs | 5 ----- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index abda6f7a06a2e..49b572c70c606 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -394,8 +394,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT CopyByteArrayToStream(reader, stream, cancellationToken); var length = stream.Length; - - stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); Contract.ThrowIfTrue(length != storageHandle.Identifier.Size); return ReadModuleMetadataFromStorage(storageHandle); diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index b3fd55389ce4c..d8bce5619af03 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -103,6 +103,7 @@ public TemporaryTextStorage AttachTemporaryTextStorage( public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { + stream.Position = 0; var storage = new TemporaryStreamStorage(this); storage.WriteStream(stream, cancellationToken); var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 26efdb3f3417c..adc0a016316a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -23,7 +23,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can /// be used to identify the data across processes allowing it to be read back in in any process. Note: the data - /// will not longer be readonable if the returned is disposed. + /// will not longer be readable if the returned is disposed. /// /// /// This type is used for two purposes. @@ -41,6 +41,9 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// when we get the next large compiler command line. /// /// + /// Note: The stream provided mus support . The stream will also be reset to 0 within this method. The caller does not need to reset the stream + /// itself. /// TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index de638eb1272de..738cbef3f6f72 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -28,6 +28,7 @@ public ITemporaryTextStorageInternal CreateTemporaryTextStorage() public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { + stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index b2ea809fa6420..469d8ec96427e 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -85,8 +85,6 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments writer.WriteLine(value); writer.Flush(); - stream.Position = 0; - _commandLineStorageHandle = _temporaryStorageService.WriteToTemporaryStorage(stream, CancellationToken.None); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index dafd5f5e383c7..a58bb1e08b151 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -254,7 +254,6 @@ public readonly SkeletonReferenceCache Clone() // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the // assembly-metadata point directly to that pointer in memory, instead of it having to make its // own copy it needs to own the lifetime of. - stream.Position = 0; var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); // Now read the data back from the stream from the memory mapped file. This will come back as an diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 9da9edc7d6aa8..60a50fa11da93 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -58,7 +58,6 @@ public void TestTemporaryStorageStream() data.WriteByte((byte)(i % 2)); } - data.Position = 0; var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); using var result = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); @@ -191,7 +190,6 @@ public void TestTemporaryStorageScaling() var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { - data.Position = 0; var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); storageHandles.Add(handle); } @@ -218,7 +216,6 @@ public void StreamTest1() expected.WriteByte((byte)(i % byte.MaxValue)); } - expected.Position = 0; var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; @@ -244,7 +241,6 @@ public void StreamTest2() expected.WriteByte((byte)(i % byte.MaxValue)); } - expected.Position = 0; var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; @@ -285,7 +281,6 @@ public void StreamTest3() expected.WriteByte(value); } - expected.Position = 0; var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; From cd8beb3b656042fab15a0671acebfef2649be689 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 18:50:52 -0700 Subject: [PATCH 0752/1047] Remove dispose capability --- ...emporaryStorageService.MemoryMappedInfo.cs | 29 +++-------------- .../TemporaryStorageServiceFactory.cs | 31 ++++++------------- .../TemporaryStorage/ITemporaryStorage.cs | 2 +- .../ProjectSystemProjectOptionsProcessor.cs | 2 -- .../TemporaryStorageServiceTests.cs | 8 ++--- 5 files changed, 20 insertions(+), 52 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 0bd1fd88552e7..88dcf081ba7b2 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -22,11 +22,6 @@ internal partial class TemporaryStorageService /// metadata dll shadow copy. shared view will help those cases. /// /// - /// Instances of this class should be disposed when they are no longer needed. After disposing this - /// instance, it should no longer be used. However, streams obtained through - /// or will not be invalidated until they are disposed independently (which - /// may occur before or after the is disposed. - /// /// This class and its nested types have familiar APIs and predictable behavior when used in other code, /// but are non-trivial to work on. The implementations of adhere to the best /// practices described in @@ -34,7 +29,7 @@ internal partial class TemporaryStorageService /// Update: Dispose, Finalization, and Resource Management. Additional notes regarding operating system /// behavior leveraged for efficiency are given in comments. /// - internal sealed class MemoryMappedInfo(ReferenceCountedDisposable memoryMappedFile, string name, long offset, long size) : IDisposable + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) : IDisposable { /// /// The memory mapped file. @@ -44,7 +39,7 @@ internal sealed class MemoryMappedInfo(ReferenceCountedDisposable /// - private readonly ReferenceCountedDisposable _memoryMappedFile = memoryMappedFile; + private readonly MemoryMappedFile _memoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. @@ -62,7 +57,7 @@ internal sealed class MemoryMappedInfo(ReferenceCountedDisposable.WeakReference _weakReadAccessor; public MemoryMappedInfo(string name, long offset, long size) - : this(new ReferenceCountedDisposable(MemoryMappedFile.OpenExisting(name)), name, offset, size) + : this(MemoryMappedFile.OpenExisting(name), name, offset, size) { } @@ -96,14 +91,7 @@ public UnmanagedMemoryStream CreateReadableStream() if (streamAccessor == null) { var rawAccessor = RunWithCompactingGCFallback( - static info => - { - using var memoryMappedFile = info._memoryMappedFile.TryAddReference(); - if (memoryMappedFile is null) - throw new ObjectDisposedException(typeof(MemoryMappedInfo).FullName); - - return memoryMappedFile.Target.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read); - }, + static info => info._memoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this); streamAccessor = new ReferenceCountedDisposable(rawAccessor); _weakReadAccessor = new ReferenceCountedDisposable.WeakReference(streamAccessor); @@ -120,14 +108,7 @@ public UnmanagedMemoryStream CreateReadableStream() public Stream CreateWritableStream() { return RunWithCompactingGCFallback( - static info => - { - using var memoryMappedFile = info._memoryMappedFile.TryAddReference(); - if (memoryMappedFile is null) - throw new ObjectDisposedException(typeof(MemoryMappedInfo).FullName); - - return memoryMappedFile.Target.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write); - }, + static info => info._memoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 4fa4e5cf53697..c58bd609f8619 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -36,7 +36,7 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to /// the next lower power-of-2 size for Roslyn.sln itself. /// - /// + /// private const long SingleFileThreshold = 256 * 1024; /// @@ -47,7 +47,7 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. /// - /// + /// private const long MultiFileBlockSize = SingleFileThreshold * 32; private readonly IWorkspaceThreadingService? _workspaceThreadingService; @@ -68,23 +68,23 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer /// allocation until space is no longer available in it. Access should be synchronized on /// - private ReferenceCountedDisposable.WeakReference _weakFileReference; + private MemoryMappedFile? _fileReference; /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on /// - /// + /// private string? _name; /// The total size of the current memory mapped file for multiple storage units. Access should be /// synchronized on - /// + /// private long _fileSize; /// /// The offset into the current memory mapped file where the next storage unit can be held. Access should be /// synchronized on . /// - /// + /// private long _offset; [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] @@ -125,8 +125,7 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) { // Larger blocks are allocated separately var mapName = CreateUniqueName(size); - var storage = MemoryMappedFile.CreateNew(mapName, size); - return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); + return new MemoryMappedInfo(mapName, offset: 0, size: size); } lock (_gate) @@ -134,14 +133,13 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted // handle to a memory mapped file is obtained in this section, it must either be disposed before // returning or returned to the caller who will own it through the MemoryMappedInfo. - var reference = _weakFileReference.TryAddReference(); + var reference = _fileReference; if (reference == null || _offset + size > _fileSize) { var mapName = CreateUniqueName(MultiFileBlockSize); - var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); - reference = new ReferenceCountedDisposable(file); - _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); + reference = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); + _fileReference = reference; _name = mapName; _fileSize = MultiFileBlockSize; _offset = size; @@ -330,15 +328,6 @@ public TemporaryStreamStorage(TemporaryStorageService service, string storageNam public long Offset => _memoryMappedInfo!.Offset; public long Size => _memoryMappedInfo!.Size; - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - _memoryMappedInfo = null; - } - Stream ITemporaryStreamStorageInternal.ReadStream(CancellationToken cancellationToken) => ReadStream(cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index ef647f1af7b36..8a3424a5cce7b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -41,7 +41,7 @@ internal interface ITemporaryTextStorageInternal : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -internal interface ITemporaryStreamStorageInternal : IDisposable +internal interface ITemporaryStreamStorageInternal { Stream ReadStream(CancellationToken cancellationToken = default); void WriteStream(Stream stream, CancellationToken cancellationToken = default); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 11be0ef802128..626d14c0be186 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -71,7 +71,6 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments // Dispose the existing stored command-line and then persist the new one so we can // recover it later. Only bother persisting things if we have a non-empty string. - _commandLineStorage?.Dispose(); _commandLineStorage = null; if (!arguments.IsEmpty) { @@ -276,7 +275,6 @@ public void Dispose() lock (_gate) { DisposeOfRuleSetFile_NoLock(); - _commandLineStorage?.Dispose(); } } } diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index df9924ce8f49a..0c810774c1730 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -168,9 +168,10 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - using ITemporaryStreamStorageInternal storage1 = service.CreateTemporaryStreamStorage(), - storage2 = service.CreateTemporaryStreamStorage(); - var storage3 = service.CreateTemporaryStreamStorage(); // let the finalizer run for this instance + ITemporaryStreamStorageInternal + storage1 = service.CreateTemporaryStreamStorage(), + storage2 = service.CreateTemporaryStreamStorage(), + storage3 = service.CreateTemporaryStreamStorage(); storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); @@ -225,7 +226,6 @@ public void TestTemporaryStorageScaling() { using var s = storageHandles[i].ReadStream(CancellationToken.None); Assert.Equal(1, s.ReadByte()); - storageHandles[i].Dispose(); } } } From 523837395ccf4842d91b8856af2b007b6db82ce5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 19:19:21 -0700 Subject: [PATCH 0753/1047] Simplify --- ...emporaryStorageService.MemoryMappedInfo.cs | 68 +++++-------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 88dcf081ba7b2..f71ceccc95161 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -7,7 +7,6 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Runtime; -using System.Runtime.InteropServices; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -22,35 +21,25 @@ internal partial class TemporaryStorageService /// metadata dll shadow copy. shared view will help those cases. /// /// - /// This class and its nested types have familiar APIs and predictable behavior when used in other code, - /// but are non-trivial to work on. The implementations of adhere to the best - /// practices described in - /// DG - /// Update: Dispose, Finalization, and Resource Management. Additional notes regarding operating system - /// behavior leveraged for efficiency are given in comments. + /// This class and its nested types have familiar APIs and predictable behavior when used in other code, but + /// are non-trivial to work on. /// - internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) : IDisposable + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) { /// /// The memory mapped file. /// - /// - /// It is possible for the file to be disposed prior to the view and/or the streams which use it. - /// However, the operating system does not actually close the views which are in use until the file handles - /// are closed as well, even if the file is disposed first. - /// private readonly MemoryMappedFile _memoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. /// /// - /// This holds a weak counted reference to current , which - /// allows additional accessors for the same address space to be obtained up until the point when no - /// external code is using it. When the memory is no longer being used by any objects, the view of the memory mapped file is unmapped, - /// making the process address space it previously claimed available for other purposes. If/when it is - /// needed again, a new view is created. + /// This holds a weak counted reference to current , which allows + /// additional accessors for the same address space to be obtained up until the point when no external code is + /// using it. When the memory is no longer being used by any + /// objects, the view of the memory mapped file is unmapped, making the process address space it previously + /// claimed available for other purposes. If/when it is needed again, a new view is created. /// /// This view is read-only, so it is only used by . /// @@ -84,9 +73,9 @@ public MemoryMappedInfo(string name, long offset, long size) /// public UnmanagedMemoryStream CreateReadableStream() { - // Note: TryAddReference behaves according to its documentation even if the target object has been - // disposed. If it returns non-null, then the object will not be disposed before the returned - // reference is disposed (see comments on _memoryMappedFile and TryAddReference). + // Note: TryAddReference behaves according to its documentation even if the target object has been disposed. + // If it returns non-null, then the object will not be disposed before the returned reference is disposed + // (see comments on _memoryMappedFile and TryAddReference). var streamAccessor = _weakReadAccessor.TryAddReference(); if (streamAccessor == null) { @@ -113,16 +102,15 @@ public Stream CreateWritableStream() } /// - /// Run a function which may fail with an if not enough memory is available to - /// satisfy the request. In this case, a full compacting GC pass is forced and the function is attempted - /// again. + /// Run a function which may fail with an if not enough memory is available to satisfy + /// the request. In this case, a full compacting GC pass is forced and the function is attempted again. /// /// - /// and - /// will use a native - /// memory map, which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care - /// about creating a UI delay with a full forced compacting GC. If it crashes the second try, it means we're - /// legitimately out of resources. + /// and will use a native memory map, + /// which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care about creating a UI + /// delay with a full forced compacting GC. If it crashes the second try, it means we're legitimately out of + /// resources. /// /// The type of argument to pass to the callback. /// The type returned by the function. @@ -152,42 +140,22 @@ private static void ForceCompactingGC() GC.Collect(); } - public void Dispose() - { - // See remarks on field for relation between _memoryMappedFile and the views/streams. There is no - // need to write _weakReadAccessor here since lifetime of the target is not owned by this instance. - _memoryMappedFile.Dispose(); - } - private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream : UnmanagedMemoryStream { private readonly ReferenceCountedDisposable _accessor; - private byte* _start; public MemoryMappedViewUnmanagedMemoryStream(ReferenceCountedDisposable accessor, long length) : base((byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, length) { _accessor = accessor; - _start = this.PositionPointer; } protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (disposing) - { _accessor.Dispose(); - } - - _start = null; } - - /// - /// Get underlying native memory directly. - /// - public IntPtr GetPointer() - => (IntPtr)_start; } } } From 96cbdefba41c18d13de6cd1c53b64df654861069 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 19:20:09 -0700 Subject: [PATCH 0754/1047] Simplify --- .../TemporaryStorageServiceFactory.cs | 12 ------------ .../Host/TemporaryStorage/ITemporaryStorage.cs | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index c58bd609f8619..fe58c3d1671b8 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -209,18 +209,6 @@ public TemporaryTextStorage( /// public ImmutableArray ContentHash => _contentHash; - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - - _memoryMappedInfo = null; - _encoding = null; - _contentHash = default; - } - public SourceText ReadText(CancellationToken cancellationToken) { if (_memoryMappedInfo == null) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index 8a3424a5cce7b..dc38ed43464a7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -33,7 +33,7 @@ public interface ITemporaryStreamStorage : IDisposable /// /// TemporaryStorage can be used to read and write text to a temporary storage location. /// -internal interface ITemporaryTextStorageInternal : IDisposable +internal interface ITemporaryTextStorageInternal { SourceText ReadText(CancellationToken cancellationToken = default); Task ReadTextAsync(CancellationToken cancellationToken = default); From 9b0b48034953afe2ed849cc403aecb93c40ace7a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 19:26:48 -0700 Subject: [PATCH 0755/1047] fix --- src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs | 4 ++-- .../WorkspaceServiceTests/TemporaryStorageServiceTests.cs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index d8fb050b92a0e..aa516e6d36f6e 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -86,7 +86,7 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); + var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it await temporaryStorage.WriteTextAsync(text); @@ -108,7 +108,7 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); + var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it await temporaryStorage.WriteTextAsync(text); diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 0c810774c1730..f610799684459 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -84,8 +84,6 @@ private static void TestTemporaryStorage(ITemporaryStorageServiceInternal tempor Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); Assert.Equal(text.Encoding, text2.Encoding); - - temporaryStorage.Dispose(); } [ConditionalFact(typeof(WindowsOnly))] From ec833c0ae9dd18308eabdb6587770066079b567c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 22 Apr 2024 14:30:07 +1000 Subject: [PATCH 0756/1047] Check UI context before trying to initialize cohosting --- .../ExternalAccess/Razor/Cohost/Constants.cs | 4 +++ .../RazorDynamicRegistrationServiceFactory.cs | 28 ++++++++++++++----- ...t.CodeAnalysis.ExternalAccess.Razor.csproj | 1 + 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs index 1c361a9fc7003..be036e816f43f 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.LanguageServer; @@ -16,4 +17,7 @@ internal static class Constants public const string RazorLanguageContract = ProtocolConstants.RazorCohostContract; public static readonly ImmutableArray RazorLanguage = ImmutableArray.Create("Razor"); + + // The UI context is provided by Razor, so this going must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs + public static readonly Guid RazorCohostingUIContext = new Guid("6d5b86dc-6b8a-483b-ae30-098a3c7d6774"); } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index f3f84a2e888e5..18ca571fa1314 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -9,15 +9,17 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.VisualStudio.Shell; using Newtonsoft.Json; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] IRazorCohostDynamicRegistrationService? dynamicRegistrationService) : ILspServiceFactory +internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { @@ -28,11 +30,11 @@ public ILspService CreateILspService(LspServices lspServices, WellKnownLspServer private class RazorDynamicRegistrationService : ILspService, IOnInitialized { - private readonly IRazorCohostDynamicRegistrationService? _dynamicRegistrationService; - private readonly IClientLanguageServerManager _clientLanguageServerManager; + private readonly Lazy? _dynamicRegistrationService; + private readonly IClientLanguageServerManager? _clientLanguageServerManager; private readonly JsonSerializerSettings _serializerSettings; - public RazorDynamicRegistrationService(IRazorCohostDynamicRegistrationService? dynamicRegistrationService, IClientLanguageServerManager clientLanguageServerManager) + public RazorDynamicRegistrationService(Lazy? dynamicRegistrationService, IClientLanguageServerManager? clientLanguageServerManager) { _dynamicRegistrationService = dynamicRegistrationService; _clientLanguageServerManager = clientLanguageServerManager; @@ -44,17 +46,29 @@ public RazorDynamicRegistrationService(IRazorCohostDynamicRegistrationService? d public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - if (_dynamicRegistrationService is null) + if (_dynamicRegistrationService is null || _clientLanguageServerManager is null) { return Task.CompletedTask; } + var uiContext = UIContext.FromUIContextGuid(Constants.RazorCohostingUIContext); + uiContext.WhenActivated(() => + { + // Not using the cancellation token passed in, as the context could be activated well after LSP server initialization + InitializeRazor(clientCapabilities, context, CancellationToken.None); + }); + + return Task.CompletedTask; + } + + private void InitializeRazor(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, _serializerSettings); - var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager); + var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager!); var requestContext = new RazorCohostRequestContext(context); - return _dynamicRegistrationService.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken); + _dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); } } } diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index bd93fb921807e..8cbf092883199 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -49,6 +49,7 @@ + From 26fe42993257c2e2bcc4abb0e6574941e3914f8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 00:25:10 -0700 Subject: [PATCH 0757/1047] Fixes --- .../TemporaryStorageService.MemoryMappedInfo.cs | 15 ++++++++------- .../TemporaryStorageServiceFactory.cs | 11 ++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index f71ceccc95161..04cdc93da906f 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -29,7 +29,7 @@ internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string /// /// The memory mapped file. /// - private readonly MemoryMappedFile _memoryMappedFile = memoryMappedFile; + public readonly MemoryMappedFile MemoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. @@ -45,10 +45,11 @@ internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string /// private ReferenceCountedDisposable.WeakReference _weakReadAccessor; - public MemoryMappedInfo(string name, long offset, long size) - : this(MemoryMappedFile.OpenExisting(name), name, offset, size) - { - } + public static MemoryMappedInfo OpenExisting(string name, long offset, long size) + => new(MemoryMappedFile.OpenExisting(name), name, offset, size); + + public static MemoryMappedInfo CreateNew(string name, long size) + => new(MemoryMappedFile.CreateNew(name, size), name, offset: 0, size); /// /// The name of the memory mapped file. @@ -80,7 +81,7 @@ public UnmanagedMemoryStream CreateReadableStream() if (streamAccessor == null) { var rawAccessor = RunWithCompactingGCFallback( - static info => info._memoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), + static info => info.MemoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this); streamAccessor = new ReferenceCountedDisposable(rawAccessor); _weakReadAccessor = new ReferenceCountedDisposable.WeakReference(streamAccessor); @@ -97,7 +98,7 @@ public UnmanagedMemoryStream CreateReadableStream() public Stream CreateWritableStream() { return RunWithCompactingGCFallback( - static info => info._memoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), + static info => info.MemoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index fe58c3d1671b8..f4c04c2b06f58 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -121,12 +121,9 @@ public TemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, l /// A describing the allocated block. private MemoryMappedInfo CreateTemporaryStorage(long size) { + // Larger blocks are allocated separately if (size >= SingleFileThreshold) - { - // Larger blocks are allocated separately - var mapName = CreateUniqueName(size); - return new MemoryMappedInfo(mapName, offset: 0, size: size); - } + return MemoryMappedInfo.CreateNew(CreateUniqueName(size), size: size); lock (_gate) { @@ -182,7 +179,7 @@ public TemporaryTextStorage( _checksumAlgorithm = checksumAlgorithm; _encoding = encoding; _contentHash = contentHash; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); } // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 @@ -307,7 +304,7 @@ public TemporaryStreamStorage(TemporaryStorageService service) public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) { _service = service; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); } // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 From 7abbaad682e9feb3391c22459b6c367b65f70131 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Mon, 22 Apr 2024 09:22:26 -0700 Subject: [PATCH 0758/1047] Getting MEF to work at runtime --- .../DiagnosticSourceManager.cs | 20 +++++++++---------- ...cePullDiagnosticsHandler_IOnInitialized.cs | 10 ++++++---- ...stractHotReloadDiagnosticSourceProvider.cs | 2 +- ...kspaceHotReloadDiagnosticSourceProvider.cs | 2 ++ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 1db563fed292b..03321965e977b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -17,33 +17,31 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics [Export(typeof(IDiagnosticSourceManager)), Shared] internal class DiagnosticSourceManager : IDiagnosticSourceManager { - private readonly Lazy> _documentProviders; - private readonly Lazy> _workspaceProviders; - private readonly Lazy> sourceProviders; + private readonly ImmutableDictionary _documentProviders; + private readonly ImmutableDictionary _workspaceProviders; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager(/*[ImportMany] Lazy> sourceProviders*/) + public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) { - sourceProviders = new(() => new List()); - _documentProviders = new(() => sourceProviders.Value + _documentProviders = sourceProviders .Where(p => p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - _workspaceProviders = new(() => sourceProviders.Value + _workspaceProviders = sourceProviders .Where(p => !p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); } /// public IEnumerable GetSourceNames(bool isDocument) - => (isDocument ? _documentProviders : _workspaceProviders).Value.Keys; + => (isDocument ? _documentProviders : _workspaceProviders).Keys; /// public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - if (providersDictionary.Value.TryGetValue(sourceName, out var provider)) + if (providersDictionary.TryGetValue(sourceName, out var provider)) return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); return new([]); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 4ee7886ed524e..7d3260b36184a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -14,12 +14,14 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitial public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); + var regParams = new RegistrationParams + { + Registrations = sources.Select(FromSourceName).ToArray() + }; + regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, - @params: new RegistrationParams() - { - Registrations = sources.Select(FromSourceName).ToArray() - }, + @params: regParams, cancellationToken).ConfigureAwait(false); Registration FromSourceName(string sourceName) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs index cd0a235e52fa5..dcc71a2714646 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -internal class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider +internal abstract class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider { string IDiagnosticSourceProvider.Name => "HotReloadDiagnostic"; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index 742fba39407f5..9648b2902a8c3 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -24,6 +24,8 @@ internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { + bool IDiagnosticSourceProvider.IsDocument => false; + async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { if (context.Solution is not Solution solution) From 5a009a7826ebf643ef6a1368a88cba7e8cb7df0f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 09:57:53 -0700 Subject: [PATCH 0759/1047] Simplify --- .../Host/TemporaryStorage/TemporaryStorageIdentifier.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs index 23b84ece0e996..2457297fca662 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs @@ -11,11 +11,8 @@ namespace Microsoft.CodeAnalysis.Host; /// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be /// used to identify that segment across processes, allowing for efficient sharing of data. /// -[DataContract] internal sealed record TemporaryStorageIdentifier( - [property: DataMember(Order = 0)] string Name, - [property: DataMember(Order = 1)] long Offset, - [property: DataMember(Order = 2)] long Size) + string Name, long Offset, long Size) { public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) => new( From 26430188b9c0f616ef07f9636272726939cce96c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 10:40:02 -0700 Subject: [PATCH 0760/1047] spelling --- .../Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index adc0a016316a6..5636c4b941e0b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -41,7 +41,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// when we get the next large compiler command line. /// /// - /// Note: The stream provided mus support . The stream will also be reset to . The stream will also be reset to 0 within this method. The caller does not need to reset the stream /// itself. /// From 3af1ac43c9461c9e70b22c85ed87c6284262cf9c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 10:40:41 -0700 Subject: [PATCH 0761/1047] Tweak identifier --- .../Host/TemporaryStorage/TrivialTemporaryStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 0f0df26c0a8ae..f854e7fd578a1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -31,7 +31,7 @@ public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, Cancellatio stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); var handle = new TemporaryStorageHandle(memoryMappedFile: null, identifier); s_streamStorage.Add(identifier, storage); return handle; From befe5538e51e3c9427b6aff5be420eb05a755672 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 11:01:06 -0700 Subject: [PATCH 0762/1047] cleanup --- .../VisualStudioMetadataReferenceManager.cs | 1 - .../SolutionCompilationState.SkeletonReferenceCache.cs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index ffc07f78fd511..6823a8729ce65 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -220,7 +220,6 @@ void GetStorageInfoFromTemporaryStorage( // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageHandle.Identifier, CancellationToken.None); - GC.KeepAlive(storageHandle); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index a58bb1e08b151..d030944c9521e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -260,6 +260,10 @@ public readonly SkeletonReferenceCache Clone() // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); + + // Note: because we are using a memory-mapped file, we need to keep the handle alive during + // the call to ReadFromTemporaryStorageService. Otherwise, the memory-mapped file could be + // released before we read what we need out of it. GC.KeepAlive(handle); return result; } From 31003d06277562438b38e2768b8204faa6d8dc8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 11:34:57 -0700 Subject: [PATCH 0763/1047] simplify --- .../ProjectSystem/ProjectSystemProjectOptionsProcessor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 94acb37990bc2..16878f992c9dd 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -261,8 +261,7 @@ static IEnumerable EnumerateLines( using var stream = temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); using var reader = new StreamReader(stream); - string? line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is string line) yield return line; } } From fbff6716573a75bd2b4bc5bc17811b6cb758a4aa Mon Sep 17 00:00:00 2001 From: Marco Goertz Date: Mon, 22 Apr 2024 12:10:00 -0700 Subject: [PATCH 0764/1047] Use a small subset of VS JSON converters for VS Code --- .../VSCodeInternalExtensionUtilities.cs | 52 +++++++++++++++++++ .../Protocol/RoslynLanguageServer.cs | 2 + .../Xaml/Internal/ClientCapabilityProvider.cs | 6 +++ 3 files changed, 60 insertions(+) create mode 100644 src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs new file mode 100644 index 0000000000000..a1d482e7a480a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol +{ + using Newtonsoft.Json; + + /// + /// Utilities to aid work with VS Code LSP Extensions. + /// + internal static class VSCodeInternalExtensionUtilities + { + /// + /// Adds necessary to deserialize + /// JSON stream into objects which include VS Code-specific extensions. + /// + /// + /// If is used in parallel to execution of this method, + /// its access needs to be synchronized with this method call, to guarantee that + /// collection is not modified when in use. + /// + /// Instance of which is guaranteed to not work in parallel to this method call. + public static void AddVSCodeInternalExtensionConverters(this JsonSerializer serializer) + { + // Reading the number of converters before we start adding new ones + var existingConvertersCount = serializer.Converters.Count; + + AddOrReplaceConverter(); + AddOrReplaceConverter(); + + void AddOrReplaceConverter() + where TExtension : TBase + { + for (var i = 0; i < existingConvertersCount; i++) + { + var existingConverterType = serializer.Converters[i].GetType(); + if (existingConverterType.IsGenericType && + existingConverterType.GetGenericTypeDefinition() == typeof(VSExtensionConverter<,>) && + existingConverterType.GenericTypeArguments[0] == typeof(TBase)) + { + serializer.Converters.RemoveAt(i); + existingConvertersCount--; + break; + } + } + + serializer.Converters.Add(new VSExtensionConverter()); + } + } + } +} diff --git a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs index 3dc9a54510650..f7c38ee329d77 100644 --- a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs +++ b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs @@ -39,6 +39,8 @@ public RoslynLanguageServer( _lspServiceProvider = lspServiceProvider; _serverKind = serverKind; + VSCodeInternalExtensionUtilities.AddVSCodeInternalExtensionConverters(serializer); + // Create services that require base dependencies (jsonrpc) or are more complex to create to the set manually. _baseServices = GetBaseServices(jsonRpc, logger, capabilitiesProvider, hostServices, serverKind, supportedLanguages); diff --git a/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs index e0f7cf9e77eda..812a79e593eef 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs @@ -73,6 +73,12 @@ public bool IsDynamicRegistrationSupported(string methodName) return _clientCapabilities?.Workspace?.DidChangeConfiguration?.DynamicRegistration == true; case LSP.Methods.WorkspaceDidChangeWatchedFilesName: return _clientCapabilities?.Workspace?.DidChangeWatchedFiles?.DynamicRegistration == true; + case LSP.VSInternalMethods.OnAutoInsertName: + if (_clientCapabilities.TextDocument is VSInternalTextDocumentClientCapabilities internalTextDocumentClientCapabilities) + { + return internalTextDocumentClientCapabilities.OnAutoInsert?.DynamicRegistration == true; + } + return false; default: throw new InvalidOperationException($"Unsupported dynamic registration method: {methodName}"); } From f9d834664670725a8f903ea9a41269149137d6fa Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 22 Apr 2024 12:22:27 -0700 Subject: [PATCH 0765/1047] Add test --- ...alStudio.LanguageServices.UnitTests.vbproj | 3 ++ .../VisualBasicUnifiedSettingsTests.vb | 44 +++++++++++++++++++ .../visualBasicSettings.registration.json | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb diff --git a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 7e8b130d74ac7..0b5136477ddd7 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -62,4 +62,7 @@ + + + \ No newline at end of file diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb new file mode 100644 index 0000000000000..6e8cd59d86e98 --- /dev/null +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -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. + +Imports System.Collections.Immutable +Imports System.IO +Imports System.Reflection +Imports Microsoft.CodeAnalysis.Completion +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.VisualStudio.LanguageServices +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings +Imports Newtonsoft.Json.Linq + +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 + + + Public Async Function IntelliSensePageTests() As Task + Dim registrationFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("visualBasicSettings.registration.json") + Using reader = New StreamReader(registrationFileStream) + Dim registrationFile = Await reader.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()) + End Using + End Function +End Class diff --git a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json index 7bba9c4161e1b..c744cc9dc0357 100644 --- a/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json +++ b/src/VisualStudio/VisualBasic/Impl/UnifiedSettings/visualBasicSettings.registration.json @@ -179,7 +179,7 @@ }, "textEditor.basic.intellisense": { "title": "@112;{574fc912-f74f-4b4e-92c3-f695c208a2bb}", - "legacyOptionPageId": "EDE66829-7A36-4c5d-8E20-9290195DCF80" + "legacyOptionPageId": "04460A3B-1B5F-4402-BC6D-89A4F6F0A8D7" } } } From cbc855e0e3b1859433c6922d72b6238c516945b7 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 22 Apr 2024 12:34:06 -0700 Subject: [PATCH 0766/1047] Override some behaviors --- .../VisualBasicUnifiedSettingsTests.vb | 63 +++++++++++++------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb index 6e8cd59d86e98..ffc5dbfabaeed 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -5,18 +5,20 @@ 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 -Public Class VisualBasicUnifiedSettingsTests - Inherits UnifiedSettingsTests +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)( + Friend Overrides ReadOnly Property OnboardedOptions As ImmutableArray(Of IOption2) + Get + Return ImmutableArray.Create(Of IOption2)( CompletionOptionsStorage.TriggerOnTypingLetters, CompletionOptionsStorage.TriggerOnDeletion, CompletionViewOptionsStorage.HighlightMatchingPortionsOfCompletionListItems, @@ -26,19 +28,40 @@ Public Class VisualBasicUnifiedSettingsTests CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, CompletionViewOptionsStorage.EnableArgumentCompletionSnippets ) - End Get - End Property + End Get + End Property - - Public Async Function IntelliSensePageTests() As Task - Dim registrationFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("visualBasicSettings.registration.json") - Using reader = New StreamReader(registrationFileStream) - Dim registrationFile = Await reader.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()) - End Using - End Function -End Class + 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 + Return MyBase.GetOptionsDefaultValue([option]) + End Function + + + Public Async Function IntelliSensePageTests() As Task + Dim registrationFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("visualBasicSettings.registration.json") + Using reader = New StreamReader(registrationFileStream) + Dim registrationFile = Await reader.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) + End Using + End Function + End Class +End Namespace From 9165bbb20fe7b7c6a5336db405b8e753a98859e9 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 22 Apr 2024 12:46:32 -0700 Subject: [PATCH 0767/1047] Add other overrides --- .../VisualBasicUnifiedSettingsTests.vb | 13 +++++++++++++ .../Options/IntelliSenseOptionPageControl.xaml.vb | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb index ffc5dbfabaeed..ad13535ffc921 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Options Imports Microsoft.VisualStudio.LanguageServices Imports Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings +Imports Microsoft.VisualStudio.ProjectSystem Imports Newtonsoft.Json.Linq Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings @@ -47,6 +48,18 @@ Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings End Function Friend Overrides Function GetOptionsDefaultValue([option] As IOption2) As Object + If [option].Equals(CompletionOptionsStorage.SnippetsBehavior) Then + Return SnippetsRule.IncludeAfterTypingIdentifierQuestionTab + ElseIf [option].Equals(CompletionOptionsStorage.EnterKeyBehavior) Then + Return EnterKeyRule.Always + ElseIf [option].Equals(CompletionOptionsStorage.TriggerOnDeletion) Then + Return True + ElseIf [option].Equals(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces) Then + Return True + ElseIf [option].Equals(CompletionViewOptionsStorage.EnableArgumentCompletionSnippets) Then + Return False + End If + Return MyBase.GetOptionsDefaultValue([option]) End Function 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 From 2e2ad38dac42067ef77e53c87616c4b9b48677fe Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 22 Apr 2024 12:50:38 -0700 Subject: [PATCH 0768/1047] Add comments --- .../VisualBasicUnifiedSettingsTests.vb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb index ad13535ffc921..c371b769a3a2d 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -10,7 +10,6 @@ Imports Microsoft.CodeAnalysis.Completion Imports Microsoft.CodeAnalysis.Options Imports Microsoft.VisualStudio.LanguageServices Imports Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings -Imports Microsoft.VisualStudio.ProjectSystem Imports Newtonsoft.Json.Linq Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings @@ -48,15 +47,27 @@ Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings 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 From cd2ec32c121be5b6c8f5faa26bd42938801d725c Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Mon, 22 Apr 2024 13:07:16 -0700 Subject: [PATCH 0769/1047] Use IDiagnosticsSource to report XAML parse errors --- .../PublicDocumentDiagnosticSourceProvider.cs | 51 +++++++++++++------ .../PublicDocumentPullDiagnosticsHandler.cs | 10 ++-- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 2 +- .../Xaml/External/IXamlDiagnosticSource.cs | 16 ++++++ .../Xaml/Internal/XamlDiagnosticSource.cs | 33 ++++++++++++ .../Internal/XamlDiagnosticSourceProvider.cs | 36 +++++++++++++ .../Xaml/InternalAPI.Unshipped.txt | 6 +++ 7 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs create mode 100644 src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs create mode 100644 src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs index 0880b465d18ae..a992885c06e04 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs @@ -2,33 +2,52 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using System.Collections.Generic; using System.Collections.Immutable; -using System.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class PublicDocumentDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentDiagnosticSourceProvider(All) +/// +/// Aggregates multiple source diagnostics +/// +internal sealed class PublicDocumentDiagnosticSource : AbstractDocumentDiagnosticSource { - public const string All = "All_B69807DB-28FB-4846-884A-1152E54C8B62"; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; + private readonly string? _sourceName; - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public PublicDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, TextDocument document, string? sourceName) : base(document) { - var textDocument = AbstractDocumentDiagnosticSourceProvider.GetOpenDocument(context); - if (textDocument is null) - return new([]); + _diagnosticSourceManager = diagnosticSourceManager; + _sourceName = sourceName; + } + + public override bool IsLiveSource() => true; - var source = new DocumentDiagnosticSource(diagnosticAnalyzerService, DiagnosticKind.All /* IS THIS RIGHT ???*/, textDocument); - return new([source]); + public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + var isLive = this.IsLiveSource(); + foreach (var name in GetSourceNames()) + { + var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); + foreach (var source in sources) + { + if (source.IsLiveSource() == isLive) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + } + } + + return diagnostics.ToImmutableAndClear(); } + + private IEnumerable GetSourceNames() + => string.IsNullOrEmpty(this._sourceName) ? _diagnosticSourceManager.GetSourceNames(isDocument: true) : [_sourceName]; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 7600bc2f1ee7d..c64cffaf2496d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. -// See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentDiagnosticParams using DocumentDiagnosticPartialReport = SumType; using DocumentDiagnosticReport = SumType; @@ -93,8 +93,12 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) { - if (diagnosticParams.Identifier is string sourceName) - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + if (context.TextDocument is { } document) + { + // Wrap all sources into ISourceProvider so that we can keep using DocumentDiagnosticReport + // (which supports a single source) + return new([new PublicDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); + } return new([]); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index bc4fa71d8804e..2a52101e93329 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType; -internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler, IOnInitialized +internal sealed partial class PublicDocumentPullDiagnosticsHandler// : AbstractDocumentPullDiagnosticHandler, IOnInitialized { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs new file mode 100644 index 0000000000000..0b55c2089007d --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal interface IXamlDiagnosticSource +{ + Task> GetDiagnosticsAsync( + XamlRequestContext context, + CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs new file mode 100644 index 0000000000000..439572e17b058 --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource +{ + Project IDiagnosticSource.GetProject() => document.Project; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); + + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + bool IDiagnosticSource.IsLiveSource() => true; + + async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var xamlRequestContext = XamlRequestContext.FromRequestContext(context); + var diagnostics = await xamlDiagnosticSource.GetDiagnosticsAsync(xamlRequestContext, cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return result; + } +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..d9b7b043cfe1d --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = true)] IXamlDiagnosticSource? xamlDiagnosticSource) : IDiagnosticSourceProvider +{ + bool IDiagnosticSourceProvider.IsDocument => true; + + string IDiagnosticSourceProvider.Name => "XamlDiagnosticSource"; + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (xamlDiagnosticSource != null && context.TextDocument is { } document && + document.Project.GetAdditionalDocument(document.Id) != null) + { + return new([new XamlDiagnosticSource(xamlDiagnosticSource, document)]); + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt index d62fe5bb29af1..c8d63468defae 100644 --- a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt @@ -37,6 +37,8 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ILocationService.GetSymbolLocationsAs Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.FromResolveData(object? resolveData) -> (object? data, System.Uri? uri) Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.ToResolveData(object! data, System.Uri! uri) -> object! +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.HandleRequestAsync(TRequest request, Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.MutatesSolutionState.get -> bool @@ -51,6 +53,10 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ResolveDataConversions Microsoft.CodeAnalysis.ExternalAccess.Xaml.StringConstants Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute.XamlCommandAttribute(string! command) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource.XamlDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource! xamlDiagnosticSource, Microsoft.CodeAnalysis.TextDocument! document) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider.XamlDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource? xamlDiagnosticSource) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute.XamlMethodAttribute(string! method) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext From 0b1dd0eb992a9d3d8a68cc6baa06ebaeae7e18fe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 13:14:53 -0700 Subject: [PATCH 0770/1047] use var --- .../WorkspaceServiceTests/TemporaryStorageServiceTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index e8280a88731fa..536807def8364 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -149,9 +149,9 @@ public void TestTemporaryStorageMemoryMappedFileManagement() await Task.Yield(); - using Stream s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); - using Stream s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); - using Stream s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); + using var s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); + using var s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); + using var s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); From 8a191b3d8ca0e7707a166510d245c0a443713bc2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 13:21:25 -0700 Subject: [PATCH 0771/1047] Ensure handles are held --- .../VisualStudioMetadataReferenceManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 6823a8729ce65..3f6a31a958081 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -246,17 +246,25 @@ static void StreamCopy(Stream source, Stream destination, int start, int length) /// private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey fileKey) { - return CreateAssemblyMetadata(fileKey, fileKey => + using var _ = ArrayBuilder.GetInstance(out var storageHandles); + var newMetadata = CreateAssemblyMetadata(fileKey, fileKey => { var metadata = TryCreateModuleMetadataFromMetadataImporter(fileKey); // getting metadata didn't work out through importer. fallback to shadow copy one if (metadata == null) - GetMetadataFromTemporaryStorage(fileKey, out _, out metadata); + { + GetMetadataFromTemporaryStorage(fileKey, out var storageHandle, out metadata); + storageHandles.Add(storageHandle); + } return metadata; }); + s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); + + return newMetadata; + ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey) { if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length)) From 950eaed3116d5df8569b50a3b0f9767e42f87b7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 13:29:59 -0700 Subject: [PATCH 0772/1047] unify code --- .../VisualStudioMetadataReferenceManager.cs | 57 +++++++------------ 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 3f6a31a958081..c62c8a82e1631 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -154,36 +154,23 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storageHandles); - var newMetadata = CreateAssemblyMetadata(key, key => - { - // - // - GetMetadataFromTemporaryStorage(key, out var storageHandle, out var metadata); - storageHandles.Add(storageHandle); - return metadata; - }); - - s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); - - return newMetadata; + return CreateAssemblyMetadata(key, GetMetadataFromTemporaryStorage); } } } - private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out ModuleMetadata metadata) + private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) GetMetadataFromTemporaryStorage( + FileKey moduleFileKey) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageHandle, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storageHandle, out var stream); unsafe { // For an unmanaged memory stream, ModuleMetadata can take ownership directly. - metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); + var metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); + return (metadata, storageHandle); } - return; - void GetStorageInfoFromTemporaryStorage( FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out UnmanagedMemoryStream stream) { @@ -246,25 +233,16 @@ static void StreamCopy(Stream source, Stream destination, int start, int length) /// private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey fileKey) { - using var _ = ArrayBuilder.GetInstance(out var storageHandles); - var newMetadata = CreateAssemblyMetadata(fileKey, fileKey => + return CreateAssemblyMetadata(fileKey, fileKey => { var metadata = TryCreateModuleMetadataFromMetadataImporter(fileKey); + if (metadata != null) + return (metadata, storageHandle: null); // getting metadata didn't work out through importer. fallback to shadow copy one - if (metadata == null) - { - GetMetadataFromTemporaryStorage(fileKey, out var storageHandle, out metadata); - storageHandles.Add(storageHandle); - } - - return metadata; + return GetMetadataFromTemporaryStorage(fileKey); }); - s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); - - return newMetadata; - ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey) { if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length)) @@ -319,11 +297,13 @@ bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] /// private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, - Func moduleMetadataFactory) + Func moduleMetadataFactory) { - var manifestModule = moduleMetadataFactory(fileKey); + using var _1 = ArrayBuilder.GetInstance(out var storageHandles); + var (manifestModule, storageHandle) = moduleMetadataFactory(fileKey); + storageHandles.AddIfNotNull(storageHandle); - using var _ = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _2 = ArrayBuilder.GetInstance(out var moduleBuilder); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) @@ -336,14 +316,17 @@ private static AssemblyMetadata CreateAssemblyMetadata( // Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636 var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName)!); - var metadata = moduleMetadataFactory(moduleFileKey); + var (metadata, metadataStorageHandle) = moduleMetadataFactory(moduleFileKey); moduleBuilder.Add(metadata); + storageHandles.AddIfNotNull(metadataStorageHandle); } if (moduleBuilder.Count == 0) moduleBuilder.Add(manifestModule); - return AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + return result; } } From dcea7cf8a00629ac540970ad9e2902239b3ea385 Mon Sep 17 00:00:00 2001 From: Shen Chen Date: Mon, 22 Apr 2024 14:16:40 -0700 Subject: [PATCH 0773/1047] Remove a bad conflict after merging from main --- src/VisualStudio/VisualBasic/Impl/VSPackage.resx | 6 ------ src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf | 10 ---------- .../VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf | 10 ---------- src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf | 10 ---------- .../VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf | 10 ---------- .../VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf | 10 ---------- 14 files changed, 136 deletions(-) diff --git a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx index 0b793eb6682c6..00e7dc9bbac3c 100644 --- a/src/VisualStudio/VisualBasic/Impl/VSPackage.resx +++ b/src/VisualStudio/VisualBasic/Impl/VSPackage.resx @@ -208,12 +208,6 @@ Use enhanced colors;Editor Color Scheme;Inheritance Margin;Import Directives;Visual Basic Tools Help > About - - Visual Basic Script - - - An empty Visual Basic script file. - Always add new line on enter diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf index b777e51b2818b..276c0e8c18faf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.cs.xlf @@ -140,11 +140,6 @@ Používat rozšířené barvy;Barevné schéma editoru;Okraj dědičnosti;Direk Always include snippets - - An empty Visual Basic script file. - Prázdný soubor skriptu Visual Basicu - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Používat rozšířené barvy;Barevné schéma editoru;Okraj dědičnosti;Direk Only add new line on enter after end of fully typed word - - Visual Basic Script - Skript Visual Basicu - - \ 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 1873daf4c5d7a..a3e9414b14f48 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.de.xlf @@ -140,11 +140,6 @@ Erweiterte Farben verwenden;Editor-Farbschema;Vererbungsspielraum;Richtlinien im Always include snippets - - An empty Visual Basic script file. - Eine leere Visual Basic-Skriptdatei. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Erweiterte Farben verwenden;Editor-Farbschema;Vererbungsspielraum;Richtlinien im Only add new line on enter after end of fully typed word - - Visual Basic Script - Visual Basic-Skript - - \ 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 82579f109165d..a318fc614937a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.es.xlf @@ -140,11 +140,6 @@ Usar colores mejorados;Combinación de colores del editor;Margen de herencia;Dir Always include snippets - - An empty Visual Basic script file. - Un archivo de script de Visual Basic vacío. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Usar colores mejorados;Combinación de colores del editor;Margen de herencia;Dir Only add new line on enter after end of fully typed word - - Visual Basic Script - Script de Visual Basic - - \ 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 42563e8a2376f..d202187102890 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.fr.xlf @@ -140,11 +140,6 @@ Utiliser des couleurs améliorées ; Modèle de couleurs de l’éditeur;Marge d Always include snippets - - An empty Visual Basic script file. - Fichier de script Visual Basic vide. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Utiliser des couleurs améliorées ; Modèle de couleurs de l’éditeur;Marge d Only add new line on enter after end of fully typed word - - Visual Basic Script - Script Visual Basic - - \ 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 05accaa59941e..0ffbe854456b1 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.it.xlf @@ -140,11 +140,6 @@ Usare colori avanzati; Combinazione colori editor;Margine di ereditarietà;Diret Always include snippets - - An empty Visual Basic script file. - File script di Visual Basic vuoto. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Usare colori avanzati; Combinazione colori editor;Margine di ereditarietà;Diret Only add new line on enter after end of fully typed word - - Visual Basic Script - Script di Visual Basic - - \ 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 a31efbd03dec8..8b06a20801ee9 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ja.xlf @@ -140,11 +140,6 @@ JSON 文字列のエディター機能の検出と提供; Always include snippets - - An empty Visual Basic script file. - 空の Visual Basic スクリプト ファイル。 - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ JSON 文字列のエディター機能の検出と提供; Only add new line on enter after end of fully typed word - - Visual Basic Script - Visual Basic スクリプト - - \ 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 85bdf889d5716..efb4258efa09a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ko.xlf @@ -140,11 +140,6 @@ JSON 문자열 색상 지정, Always include snippets - - An empty Visual Basic script file. - 비어 있는 Visual Basic 스크립트 파일입니다. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ JSON 문자열 색상 지정, Only add new line on enter after end of fully typed word - - Visual Basic Script - Visual Basic 스크립트 - - \ 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 9447aef3a76fe..2a996c13f65dd 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pl.xlf @@ -140,11 +140,6 @@ Używanie rozszerzonych kolorów; Schemat kolorów edytora;Margines dziedziczeni Always include snippets - - An empty Visual Basic script file. - Pusty plik skryptu języka Visual Basic. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Używanie rozszerzonych kolorów; Schemat kolorów edytora;Margines dziedziczeni Only add new line on enter after end of fully typed word - - Visual Basic Script - Skrypt języka Visual Basic - - \ 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 c3f19f2b9de76..88970da2f6d56 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.pt-BR.xlf @@ -140,11 +140,6 @@ Usar cores aprimoradas;Esquema de Cores do Editor;Margem de Herança;Importar Di Always include snippets - - An empty Visual Basic script file. - Um arquivo de script vazio do Visual Basic. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Usar cores aprimoradas;Esquema de Cores do Editor;Margem de Herança;Importar Di Only add new line on enter after end of fully typed word - - Visual Basic Script - Script do Visual Basic - - \ 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 9f70116b86c8e..63dbfc68c0bb5 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.ru.xlf @@ -140,11 +140,6 @@ JSON; Always include snippets - - An empty Visual Basic script file. - Пустой файл сценария Visual Basic. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ JSON; Only add new line on enter after end of fully typed word - - Visual Basic Script - Сценарий Visual Basic - - \ 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 3cb7fe4120bd5..10bf015c805aa 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.tr.xlf @@ -140,11 +140,6 @@ Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni;Devralma Kenar Boşluğu;İ Always include snippets - - An empty Visual Basic script file. - Boş bir Visual Basic betik dosyası. - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ Gelişmiş renkleri kullan;Düzenleyici Renk Düzeni;Devralma Kenar Boşluğu;İ Only add new line on enter after end of fully typed word - - Visual Basic Script - Visual Basic Betiği - - \ 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 d4bb49956fb72..9dc70f453cd84 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hans.xlf @@ -140,11 +140,6 @@ JSON; Always include snippets - - An empty Visual Basic script file. - 空的 Visual Basic 脚本文件。 - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ JSON; Only add new line on enter after end of fully typed word - - Visual Basic Script - Visual Basic 脚本 - - \ 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 e336be864f31b..06fd1d217bfde 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/VSPackage.zh-Hant.xlf @@ -140,11 +140,6 @@ JSON; Always include snippets - - An empty Visual Basic script file. - 空白的 Visual Basic 指令碼檔。 - - Include snippets when ?-Tab is typed after an identifier Include snippets when ?-Tab is typed after an identifier @@ -165,11 +160,6 @@ JSON; Only add new line on enter after end of fully typed word - - Visual Basic Script - Visual Basic 指令碼 - - \ No newline at end of file From 350329a08a255637b949af9b9d2c690cebfda233 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 07:18:57 +1000 Subject: [PATCH 0774/1047] Update src/Tools/ExternalAccess/Razor/Cohost/Constants.cs Co-authored-by: ady109 <149323961+ady109@users.noreply.github.com> --- src/Tools/ExternalAccess/Razor/Cohost/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs index be036e816f43f..71a70b2de65d9 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs @@ -18,6 +18,6 @@ internal static class Constants public static readonly ImmutableArray RazorLanguage = ImmutableArray.Create("Razor"); - // The UI context is provided by Razor, so this going must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs + // The UI context is provided by Razor, so this guid must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs public static readonly Guid RazorCohostingUIContext = new Guid("6d5b86dc-6b8a-483b-ae30-098a3c7d6774"); } From b5c012a9daadfb4dd9c856be6c767f3fa6e4135a Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 08:25:32 +1000 Subject: [PATCH 0775/1047] Fix build errors --- eng/Directory.Packages.props | 6 +++--- .../Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index d9970c400f1f0..64508f78d71f5 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -38,9 +38,9 @@ - - - + + + diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index 8cbf092883199..96f6c75476969 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -49,7 +49,7 @@ - + From c2f3410958ea18517595eda80104f0e6d5d8018f Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Mon, 22 Apr 2024 15:31:08 -0700 Subject: [PATCH 0776/1047] Fixing tests --- ...TypeScriptPullDiagnosticHandlerProvider.cs | 7 ++- ...AbstractWorkspacePullDiagnosticsHandler.cs | 10 +--- ... => AggregatedDocumentDiagnosticSource.cs} | 32 ++++++---- .../DiagnosticSourceManager.cs | 58 +++++++++++-------- .../IDiagnosticSourceManager.cs | 2 +- .../DocumentPullDiagnosticHandler.cs | 9 +-- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 10 ++-- .../PublicDocumentPullDiagnosticsHandler.cs | 3 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 45 +++++++++----- 9 files changed, 100 insertions(+), 76 deletions(-) rename src/Features/LanguageServer/Protocol/Handler/Diagnostics/{Public/PublicDocumentDiagnosticSourceProvider.cs => AggregatedDocumentDiagnosticSource.cs} (62%) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs index da2e663f06bfd..b5527e89596a0 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; @@ -17,8 +18,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } @@ -28,7 +30,8 @@ internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( internal class VSTypeScriptWorkspacePullDiagnosticHandler( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index c0872bae69c2d..4e97b9b6231e0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -53,14 +53,8 @@ public void Dispose() protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - if (GetDiagnosticCategory(diagnosticsParams) is string sourceName) - { - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); - } - else - { - return new([]); - } + var sourceName = GetDiagnosticCategory(diagnosticsParams); + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs similarity index 62% rename from src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs index a992885c06e04..d12148e32eb62 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs @@ -4,23 +4,25 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// /// Aggregates multiple source diagnostics /// -internal sealed class PublicDocumentDiagnosticSource : AbstractDocumentDiagnosticSource +internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource { private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly string? _sourceName; - public PublicDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, TextDocument document, string? sourceName) : base(document) + public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, + TextDocument document, string? sourceName) : base(document) { _diagnosticSourceManager = diagnosticSourceManager; _sourceName = sourceName; @@ -31,17 +33,14 @@ public PublicDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceM public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var diagnostics); - var isLive = this.IsLiveSource(); foreach (var name in GetSourceNames()) { var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); foreach (var source in sources) { - if (source.IsLiveSource() == isLive) - { - var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); - diagnostics.AddRange(namedDiagnostics); - } + Debug.Assert(source.IsLiveSource(), "All document sources should be live"); + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); } } @@ -49,5 +48,18 @@ public override async Task> GetDiagnosticsAsync(R } private IEnumerable GetSourceNames() - => string.IsNullOrEmpty(this._sourceName) ? _diagnosticSourceManager.GetSourceNames(isDocument: true) : [_sourceName]; + { + if (this._sourceName != null) + { + yield return this._sourceName; + } + else + { + foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) + { + if (name != PullDiagnosticCategories.Task) + yield return name; + } + } + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 03321965e977b..448332f73132e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -11,40 +11,48 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceManager)), Shared] +internal class DiagnosticSourceManager : IDiagnosticSourceManager { - [Export(typeof(IDiagnosticSourceManager)), Shared] - internal class DiagnosticSourceManager : IDiagnosticSourceManager - { - private readonly ImmutableDictionary _documentProviders; - private readonly ImmutableDictionary _workspaceProviders; + private readonly ImmutableDictionary _documentProviders; + private readonly ImmutableDictionary _workspaceProviders; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) - { - _documentProviders = sourceProviders - .Where(p => p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) + { + _documentProviders = sourceProviders + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - _workspaceProviders = sourceProviders - .Where(p => !p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - } + _workspaceProviders = sourceProviders + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + } - /// - public IEnumerable GetSourceNames(bool isDocument) - => (isDocument ? _documentProviders : _workspaceProviders).Keys; + /// + public IEnumerable GetSourceNames(bool isDocument) + => (isDocument ? _documentProviders : _workspaceProviders).Keys; - /// - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + /// + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken) + { + var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; + if (sourceName != null) { - var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; if (providersDictionary.TryGetValue(sourceName, out var provider)) return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); - - return new([]); } + else if (isDocument) + { + if (context.TextDocument is { } document) + return new([new AggregatedDocumentDiagnosticSource(this, document, null)]); + } + + return new([]); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs index 3246e5cc9153c..b7849dac006e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -27,6 +27,6 @@ internal interface IDiagnosticSourceManager /// Source name. /// True for document sources and false for workspace sources. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 401bbcb314cd3..e037834c4bc80 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -74,14 +74,7 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bo protected override ValueTask> GetOrderedDiagnosticSourcesAsync( VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - if (diagnosticsParams.QueryingDiagnosticKind?.Value is string sourceName) - { - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); - } - else - { - return new([]); - } + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, diagnosticsParams.QueryingDiagnosticKind?.Value, true, cancellationToken); } protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index d937791b2aeff..a33f882167d5e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -39,7 +39,7 @@ public override ValueTask> CreateDiagnosticSou [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, - DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSyntax) + DiagnosticKind.CompilerSyntax, PullDiagnosticCategories.DocumentCompilerSyntax) { } @@ -55,18 +55,18 @@ internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, - DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) + DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) { } [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, - DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) + DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) { } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index c64cffaf2496d..19db1af4f9add 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -97,7 +96,7 @@ protected override ValueTask> GetOrderedDiagno { // Wrap all sources into ISourceProvider so that we can keep using DocumentDiagnosticReport // (which supports a single source) - return new([new PublicDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); + return new([new AggregatedDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); } return new([]); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 7d3260b36184a..ed55a6164df3f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public @@ -13,24 +14,38 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitial { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); - var regParams = new RegistrationParams + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) { - Registrations = sources.Select(FromSourceName).ToArray() - }; - regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics - await _clientLanguageServerManager.SendRequestAsync( - methodName: Methods.ClientRegisterCapabilityName, - @params: regParams, - cancellationToken).ConfigureAwait(false); + var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); + var regParams = new RegistrationParams + { + Registrations = sources.Select(FromSourceName).ToArray() + }; + regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics + await _clientLanguageServerManager.SendRequestAsync( + methodName: Methods.ClientRegisterCapabilityName, + @params: regParams, + cancellationToken).ConfigureAwait(false); - Registration FromSourceName(string sourceName) - => new() + Registration FromSourceName(string sourceName) + => new() + { + Id = sourceName, + Method = Methods.WorkspaceDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + }; + } + + bool IsFsaEnabled() { - Id = sourceName, - Method = Methods.WorkspaceDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } - }; + foreach (var language in context.SupportedLanguages) + { + if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) + return true; + } + + return false; + } } } } From 78fac7d962617ce140f0856364050e2c676c0289 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 22 Apr 2024 15:48:38 -0700 Subject: [PATCH 0777/1047] Remove reflection code for C# code mapper --- .../CodeMapper/CopilotCSharpMapCodeService.cs | 52 +------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs b/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs index aa8265e7d4dbb..7a20c142784b2 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper; @@ -16,8 +15,6 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper; -using MapCodeAsyncDelegateType = Func, ImmutableArray<(Document Document, TextSpan TextSpan)>, Dictionary, CancellationToken, Task?>>; - [ExportLanguageService(typeof(IMapCodeService), language: LanguageNames.CSharp), Shared] internal sealed class CSharpMapCodeService : IMapCodeService { @@ -25,9 +22,9 @@ internal sealed class CSharpMapCodeService : IMapCodeService [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpMapCodeService([Import(AllowDefault = true)] ICSharpCopilotMapCodeService? service) + public CSharpMapCodeService(ICSharpCopilotMapCodeService service) { - _service = service ?? new ReflectionWrapper(); + _service = service; } public Task?> MapCodeAsync(Document document, ImmutableArray contents, ImmutableArray<(Document, TextSpan)> focusLocations, CancellationToken cancellationToken) @@ -35,49 +32,4 @@ public CSharpMapCodeService([Import(AllowDefault = true)] ICSharpCopilotMapCodeS var options = new Dictionary(); return _service.MapCodeAsync(document, contents, focusLocations, options, cancellationToken); } - - private sealed class ReflectionWrapper : ICSharpCopilotMapCodeService - { - private const string CodeMapperDllName = "Microsoft.VisualStudio.Copilot.CodeMappers.CSharp, Version=0.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; - private const string MapCodeServiceTypeFullName = "Microsoft.VisualStudio.Conversations.CodeMappers.CSharp.CSharpMapCodeService"; - private const string MapCodeAsyncMethodName = "MapCodeAsync"; - - // Create and cache the delegate to ensure we use a singleton and better performance. - private readonly Lazy _lazyMapCodeAsyncDelegate = new(CreateDelegate, LazyThreadSafetyMode.PublicationOnly); - - private static MapCodeAsyncDelegateType? CreateDelegate() - { - try - { - var assembly = Assembly.Load(CodeMapperDllName); - var type = assembly.GetType(MapCodeServiceTypeFullName); - if (type is null) - return null; - - var serviceInstance = Activator.CreateInstance(type); - if (serviceInstance is null) - return null; - - if (type.GetMethod(MapCodeAsyncMethodName, [typeof(Document), typeof(ImmutableArray), typeof(ImmutableArray<(Document Document, TextSpan TextSpan)>), typeof(Dictionary), typeof(CancellationToken)]) is not MethodInfo mapCodeAsyncMethod) - return null; - - return (MapCodeAsyncDelegateType)Delegate.CreateDelegate(typeof(MapCodeAsyncDelegateType), serviceInstance, mapCodeAsyncMethod); - - } - catch - { - // Catch all here since failure is expected if user has no copilot chat or an older version of it installed. - } - - return null; - } - - public async Task?> MapCodeAsync(Document document, ImmutableArray contents, ImmutableArray<(Document document, TextSpan textSpan)> prioritizedFocusLocations, Dictionary options, CancellationToken cancellationToken) - { - if (_lazyMapCodeAsyncDelegate.Value is null) - return null; - - return await _lazyMapCodeAsyncDelegate.Value(document, contents, prioritizedFocusLocations, options, cancellationToken).ConfigureAwait(false); - } - } } From f8df12304b6dcf9e51b4a353ae6fe396d66011ca Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 22 Apr 2024 15:50:03 -0700 Subject: [PATCH 0778/1047] Fix example in cookbook (#73184) --- docs/features/incremental-generators.cookbook.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/incremental-generators.cookbook.md b/docs/features/incremental-generators.cookbook.md index 9bbb0b9134c9d..3cf0870f6bf57 100644 --- a/docs/features/incremental-generators.cookbook.md +++ b/docs/features/incremental-generators.cookbook.md @@ -191,7 +191,7 @@ public class FileTransformGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var pipeline = context.AdditionalTextsProvider - .Where(static (text, cancellationToken) => text.Path.EndsWith(".xml")) + .Where(static (text) => text.Path.EndsWith(".xml")) .Select(static (text, cancellationToken) => { var name = Path.GetFileName(text.Path); From 43ae3a426164384b74ee137117c84da2ded04400 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 16:17:21 -0700 Subject: [PATCH 0779/1047] fixup --- .../VisualStudioMetadataReferenceManager.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index c62c8a82e1631..ab17d0e8033e2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -166,7 +166,9 @@ AssemblyMetadata GetMetadataWorker() unsafe { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Passing in stream.Dispose + // here will also ensure that as long as this metdata is alive, we'll keep the memory-mapped-file it points + // to alive. var metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); return (metadata, storageHandle); } @@ -299,9 +301,9 @@ private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, Func moduleMetadataFactory) { - using var _1 = ArrayBuilder.GetInstance(out var storageHandles); + using var _1 = ArrayBuilder.GetInstance(out var storageHandles); var (manifestModule, storageHandle) = moduleMetadataFactory(fileKey); - storageHandles.AddIfNotNull(storageHandle); + storageHandles.Add(storageHandle); using var _2 = ArrayBuilder.GetInstance(out var moduleBuilder); @@ -319,14 +321,20 @@ private static AssemblyMetadata CreateAssemblyMetadata( var (metadata, metadataStorageHandle) = moduleMetadataFactory(moduleFileKey); moduleBuilder.Add(metadata); - storageHandles.AddIfNotNull(metadataStorageHandle); + storageHandles.Add(metadataStorageHandle); } if (moduleBuilder.Count == 0) moduleBuilder.Add(manifestModule); var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); - s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + + // If we got any null handles, then we weren't able to map this whole assembly into memory mapped files. So we + // can't use those to transfer over the data efficiently to the OOP process. In that case, we don't store the + // handles at all. + if (storageHandles.Count > 0 && storageHandles.All(h => h != null)) + s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + return result; } } From a4cfcd3f7770e813897f6f1b87027adfb2a2092a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 16:59:06 -0700 Subject: [PATCH 0780/1047] Simplify --- .../Host/TemporaryStorage/TemporaryStorageHandle.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs index fe626adafa692..b4a7343dfdbc1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs @@ -17,13 +17,7 @@ namespace Microsoft.CodeAnalysis.Host; /// internal sealed class TemporaryStorageHandle(MemoryMappedFile? memoryMappedFile, TemporaryStorageIdentifier identifier) { -#pragma warning disable IDE0052 // Remove unread private members - /// - /// This field is intentionally not read. It exists just to root the memory mapped file and keep it alive as long - /// as this handle is alive. - /// - private readonly MemoryMappedFile? _memoryMappedFile = memoryMappedFile; -#pragma warning restore IDE0052 // Remove unread private members + public readonly MemoryMappedFile? MemoryMappedFile = memoryMappedFile; private readonly TemporaryStorageIdentifier? _identifier = identifier; public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); From fb243c241804058d6d6b273b2afae750e31fe149 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:03:00 -0700 Subject: [PATCH 0781/1047] remove gc.keepalive --- ...lutionCompilationState.SkeletonReferenceCache.cs | 13 +++++-------- ...SolutionCompilationState.SkeletonReferenceSet.cs | 4 ++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index d030944c9521e..6a7b2b9eca75c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,7 +218,7 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var metadata = TryCreateMetadata(); + var (metadata, storageHandle) = TryCreateMetadataAndHandle(); if (metadata == null) return null; @@ -226,10 +226,11 @@ public readonly SkeletonReferenceCache Clone() // the stream as well. return new SkeletonReferenceSet( metadata, + storageHandle, compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - AssemblyMetadata? TryCreateMetadata() + (AssemblyMetadata? metadata, TemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() { cancellationToken.ThrowIfCancellationRequested(); @@ -261,11 +262,7 @@ public readonly SkeletonReferenceCache Clone() var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); - // Note: because we are using a memory-mapped file, we need to keep the handle alive during - // the call to ReadFromTemporaryStorageService. Otherwise, the memory-mapped file could be - // released before we read what we need out of it. - GC.KeepAlive(handle); - return result; + return (result, handle); } if (logger != null) @@ -287,7 +284,7 @@ public readonly SkeletonReferenceCache Clone() m["Errors"] = string.Join(";", groups); })); - return null; + return (null, null!); } } finally diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 033c900fecff5..4d4020f6ec71c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis; @@ -18,6 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, + TemporaryStorageHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -29,6 +31,8 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; + public TemporaryStorageHandle StorageHandle => storageHandle; + public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { lock (_referenceMap) From 27b96ea1b39bc6705acf7ad218f6aab099f51295 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:05:20 -0700 Subject: [PATCH 0782/1047] naming --- .../TemporaryStorageServiceTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 536807def8364..83d5808b72ad2 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -143,15 +143,15 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - var storage1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); - var storage2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); - var storage3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); + var handle1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); + var handle2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); + var handle3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); - using var s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); - using var s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); - using var s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); + using var s1 = service.ReadFromTemporaryStorageService(handle1.Identifier, CancellationToken.None); + using var s2 = service.ReadFromTemporaryStorageService(handle2.Identifier, CancellationToken.None); + using var s3 = service.ReadFromTemporaryStorageService(handle3.Identifier, CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); From 40f0c742134d6d31052c7953f5dc2f0577dfbcf2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:43:30 -0700 Subject: [PATCH 0783/1047] Simplify handle api --- .../VisualStudioMetadataReference.Snapshot.cs | 2 +- .../VisualStudioMetadataReferenceManager.cs | 4 +- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 8 ++-- .../TemporaryStorageService.cs | 39 ++++++++++++------- .../ITemporaryStorageService.cs | 19 +++------ .../TemporaryStorageHandle.cs | 16 +++++--- .../TrivialTemporaryStorageService.cs | 18 ++++----- .../ProjectSystemProjectOptionsProcessor.cs | 9 ++--- ...CompilationState.SkeletonReferenceCache.cs | 4 +- ...onCompilationState.SkeletonReferenceSet.cs | 4 +- .../TemporaryStorageServiceTests.cs | 20 +++++----- 12 files changed, 78 insertions(+), 67 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 8c407e68825d5..81388674db446 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageHandles + public IReadOnlyList StorageHandles => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index ab17d0e8033e2..cee1d9ecc31d6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -22,6 +22,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using static TemporaryStorageService; + /// /// Manages metadata references for VS projects. /// @@ -208,7 +210,7 @@ void GetStorageInfoFromTemporaryStorage( } // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. - stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageHandle.Identifier, CancellationToken.None); + stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index ffb6efafc2523..84dc4dc0834bd 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageHandles { get; } + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 49b572c70c606..d81de69768748 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -16,6 +16,8 @@ namespace Microsoft.CodeAnalysis.Serialization; +using static TemporaryStorageService; + internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; @@ -307,7 +309,7 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( PortableExecutableReference reference, - IReadOnlyList handles, + IReadOnlyList handles, ObjectWriter writer, CancellationToken cancellationToken) { @@ -405,7 +407,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageHandle.Identifier, cancellationToken); + var unmanagedStream = storageHandle.ReadFromTemporaryStorage(cancellationToken); Contract.ThrowIfFalse(storageHandle.Identifier.Size == unmanagedStream.Length); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as @@ -504,7 +506,7 @@ private sealed class SerializedMetadataReference : PortableExecutableReference, private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; - public IReadOnlyList StorageHandles => _storageHandles; + public IReadOnlyList StorageHandles => _storageHandles; public SerializedMetadataReference( MetadataReferenceProperties properties, diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index cd998a0d2403b..87c1bc80d50c9 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -101,28 +101,22 @@ public TemporaryTextStorage AttachTemporaryTextStorage( string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + ITemporaryStorageHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + => WriteToTemporaryStorage(stream, cancellationToken); + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new TemporaryStreamStorage(this); storage.WriteStream(stream, cancellationToken); var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(storage.MemoryMappedInfo.MemoryMappedFile, identifier); - } - - Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - - public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); - return storage.ReadStream(cancellationToken); + return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); } internal TemporaryStorageHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) { - var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); - return new(storage.MemoryMappedInfo.MemoryMappedFile, storageIdentifier); + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(this, memoryMappedFile, storageIdentifier); } /// @@ -170,6 +164,22 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) public static string CreateUniqueName(long size) => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); + public sealed class TemporaryStorageHandle( + TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + Stream ITemporaryStorageHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) + => ReadFromTemporaryStorage(cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage( + storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); + return storage.ReadStream(cancellationToken); + } + } + public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; @@ -316,10 +326,11 @@ internal sealed class TemporaryStreamStorage public TemporaryStreamStorage(TemporaryStorageService service) => _service = service; - public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) + public TemporaryStreamStorage( + TemporaryStorageService service, MemoryMappedFile file, string storageName, long offset, long size) { _service = service; - _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); + _memoryMappedInfo = new MemoryMappedInfo(file, storageName, offset, size); } public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 5636c4b941e0b..9b86919667133 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -22,17 +22,16 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService { /// /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can - /// be used to identify the data across processes allowing it to be read back in in any process. Note: the data - /// will not longer be readable if the returned is disposed. + /// be used to identify the data across processes allowing it to be read back in in any process. /// /// /// This type is used for two purposes. /// /// - /// Dumping metadata to disk. This then allowing them to be read in by mapping - /// their data into types like . It also allows them to be read in by our server - /// process, without having to transmit the data over the wire. For this use case, we never dispose of the handle, - /// opting to keep things simple by having the host and server not have to coordinate on the lifetime of the data. + /// Dumping metadata to disk. This then allowing them to be read in by mapping their data into types like . It also allows them to be read in by our server process, without having to transmit + /// the data over the wire. For this use case, we never dispose of the handle, opting to keep things simple by + /// having the host and server not have to coordinate on the lifetime of the data. /// /// /// Dumping large compiler command lines to disk to purge them from main memory. Some of these strings are enormous @@ -45,13 +44,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// cref="Stream.Position"/> 0 within this method. The caller does not need to reset the stream /// itself. /// - TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); - - /// - /// Reads the data indicated to by the into a stream. This stream can be - /// created in a different process than the one that wrote the data originally. - /// - Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken); + ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs index b4a7343dfdbc1..a1e98f500b35f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; using System.IO.MemoryMappedFiles; using System.Threading; @@ -12,13 +13,16 @@ namespace Microsoft.CodeAnalysis.Host; /// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is /// alive, the data should remain in storage and can be readable from any process using the information provided in . Use to write the data -/// to temporary storage and get a handle to it. Use to read the data back in any process. +/// to temporary storage and get a handle to it. Use to read the data back in +/// any process. /// -internal sealed class TemporaryStorageHandle(MemoryMappedFile? memoryMappedFile, TemporaryStorageIdentifier identifier) +internal interface ITemporaryStorageHandle { - public readonly MemoryMappedFile? MemoryMappedFile = memoryMappedFile; - private readonly TemporaryStorageIdentifier? _identifier = identifier; + public TemporaryStorageIdentifier Identifier { get; } - public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + /// + /// Reads the data indicated to by this handle into a stream. This stream can be created in a different process + /// than the one that wrote the data originally. + /// + Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index f854e7fd578a1..28f2b4ba4c66e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -17,8 +17,6 @@ internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceI { public static readonly TrivialTemporaryStorageService Instance = new(); - private static readonly ConditionalWeakTable s_streamStorage = new(); - private TrivialTemporaryStorageService() { } @@ -26,24 +24,24 @@ private TrivialTemporaryStorageService() public ITemporaryTextStorageInternal CreateTemporaryTextStorage() => new TextStorage(); - public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + public ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TemporaryStorageHandle(memoryMappedFile: null, identifier); - s_streamStorage.Add(identifier, storage); + var handle = new TrivialStorageHandle(identifier, storage); return handle; } - public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + private sealed class TrivialStorageHandle( + TemporaryStorageIdentifier storageIdentifier, + StreamStorage streamStorage) : ITemporaryStorageHandle { - Contract.ThrowIfFalse( - s_streamStorage.TryGetValue(storageIdentifier, out var streamStorage), - "StorageIdentifier was not created by this storage service!"); + public TemporaryStorageIdentifier Identifier => storageIdentifier; - return streamStorage.ReadStream(); + public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) + => streamStorage.ReadStream(); } private sealed class StreamStorage diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 16878f992c9dd..bca6e7a662d32 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -39,7 +39,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private TemporaryStorageHandle? _commandLineStorageHandle; + private ITemporaryStorageHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -247,7 +247,7 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // includes in the IDE so we can be watching for changes again. var commandLine = _commandLineStorageHandle == null ? ImmutableArray.Empty - : EnumerateLines(_temporaryStorageService, _commandLineStorageHandle.Identifier).ToImmutableArray(); + : EnumerateLines(_commandLineStorageHandle).ToImmutableArray(); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); @@ -255,10 +255,9 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) } static IEnumerable EnumerateLines( - ITemporaryStorageServiceInternal temporaryStorageService, - TemporaryStorageIdentifier storageIdentifier) + ITemporaryStorageHandle storageHandle) { - using var stream = temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); + using var stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); using var reader = new StreamReader(stream); while (reader.ReadLine() is string line) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 6a7b2b9eca75c..0735a5f50c9cf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -230,7 +230,7 @@ public readonly SkeletonReferenceCache Clone() compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - (AssemblyMetadata? metadata, TemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() + (AssemblyMetadata? metadata, ITemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() { cancellationToken.ThrowIfCancellationRequested(); @@ -260,7 +260,7 @@ public readonly SkeletonReferenceCache Clone() // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. var result = AssemblyMetadata.CreateFromStream( - temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); + handle.ReadFromTemporaryStorage(cancellationToken), leaveOpen: false); return (result, handle); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 4d4020f6ec71c..9d579a586ddf3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -19,7 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, - TemporaryStorageHandle storageHandle, + ITemporaryStorageHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -31,7 +31,7 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; - public TemporaryStorageHandle StorageHandle => storageHandle; + public ITemporaryStorageHandle StorageHandle => storageHandle; public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 83d5808b72ad2..b859c52a4c186 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.UnitTests { + using static TemporaryStorageService; + [UseExportProvider] #if NETCOREAPP [SupportedOSPlatform("windows")] @@ -60,7 +62,7 @@ public void TestTemporaryStorageStream() var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); - using var result = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var result = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -120,7 +122,7 @@ public void TestZeroLengthStreams() handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); } - using (var stream2 = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None)) + using (var stream2 = handle.ReadFromTemporaryStorage(CancellationToken.None)) { Assert.Equal(0, stream2.Length); } @@ -149,9 +151,9 @@ public void TestTemporaryStorageMemoryMappedFileManagement() await Task.Yield(); - using var s1 = service.ReadFromTemporaryStorageService(handle1.Identifier, CancellationToken.None); - using var s2 = service.ReadFromTemporaryStorageService(handle2.Identifier, CancellationToken.None); - using var s3 = service.ReadFromTemporaryStorageService(handle3.Identifier, CancellationToken.None); + using var s1 = handle1.ReadFromTemporaryStorage(CancellationToken.None); + using var s2 = handle2.ReadFromTemporaryStorage(CancellationToken.None); + using var s3 = handle3.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); @@ -192,7 +194,7 @@ public void TestTemporaryStorageScaling() for (var i = 0; i < 1024 * 5; i++) { - using var s = service.ReadFromTemporaryStorageService(storageHandles[i].Identifier, CancellationToken.None); + using var s = storageHandles[i].ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(1, s.ReadByte()); } } @@ -214,7 +216,7 @@ public void StreamTest1() var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) @@ -239,7 +241,7 @@ public void StreamTest2() var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); var index = 0; @@ -279,7 +281,7 @@ public void StreamTest3() var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) From 0c4240ce2a3fa5d8e7efdeabe5c8033ecad6c668 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:47:37 -0700 Subject: [PATCH 0784/1047] feedback --- .../VisualStudioMetadataReferenceManager.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index cee1d9ecc31d6..6fa856f0c688f 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -303,11 +303,10 @@ private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, Func moduleMetadataFactory) { - using var _1 = ArrayBuilder.GetInstance(out var storageHandles); - var (manifestModule, storageHandle) = moduleMetadataFactory(fileKey); - storageHandles.Add(storageHandle); + var (manifestModule, manifestHandle) = moduleMetadataFactory(fileKey); - using var _2 = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _1 = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _2 = ArrayBuilder.GetInstance(out var storageHandles); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) @@ -327,14 +326,18 @@ private static AssemblyMetadata CreateAssemblyMetadata( } if (moduleBuilder.Count == 0) + { moduleBuilder.Add(manifestModule); + storageHandles.Add(manifestHandle); + } var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); // If we got any null handles, then we weren't able to map this whole assembly into memory mapped files. So we // can't use those to transfer over the data efficiently to the OOP process. In that case, we don't store the // handles at all. - if (storageHandles.Count > 0 && storageHandles.All(h => h != null)) + Contract.ThrowIfTrue(storageHandles.Count == 0); + if (storageHandles.All(h => h != null)) s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); return result; From c64f25882f2e2aaec14186178f1fd17d95d21b62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:56:19 -0700 Subject: [PATCH 0785/1047] rename file --- .../{TemporaryStorageHandle.cs => ITemporaryStorageHandle.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/{TemporaryStorageHandle.cs => ITemporaryStorageHandle.cs} (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs rename to src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs From eedc456250a4681b245e96237d56cbf7fc680a73 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 18:01:15 -0700 Subject: [PATCH 0786/1047] cleanup --- .../Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs index a1e98f500b35f..3d2be4b29e531 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.IO; -using System.IO.MemoryMappedFiles; using System.Threading; namespace Microsoft.CodeAnalysis.Host; From 7f4f655f063bbba29fe539ea2b05184de0c7c490 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 18:13:35 -0700 Subject: [PATCH 0787/1047] usnigs --- .../ProjectSystem/ProjectSystemProjectOptionsProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index bca6e7a662d32..950cbab4a7b8a 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; From bf363528f1b974da2480f31125796caff3d92250 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 14:07:26 +1000 Subject: [PATCH 0788/1047] Revert "Fix build errors" This reverts commit b5c012a9daadfb4dd9c856be6c767f3fa6e4135a. --- eng/Directory.Packages.props | 6 +++--- .../Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 64508f78d71f5..d9970c400f1f0 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -38,9 +38,9 @@ - - - + + + diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index 96f6c75476969..bd93fb921807e 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -49,7 +49,6 @@ - From 26e7c29512a4958fd027320e4866377f8af0f1dc Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 14:44:37 +1000 Subject: [PATCH 0789/1047] Do UIContext stuff in a service so we don't need to reference VS bits --- .../Utilities/IUIContextActivationService.cs | 15 +++++ .../RazorDynamicRegistrationServiceFactory.cs | 61 ++++++++++++------- .../VisualStudioUIContextActivationService.cs | 23 +++++++ 3 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs diff --git a/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs b/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs new file mode 100644 index 0000000000000..a68e544abd1b4 --- /dev/null +++ b/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; + +internal interface IUIContextActivationService +{ + /// + /// Executes the specified action when the UIContext first becomes active, or immediately if it is already active + /// + void ExecuteWhenActivated(Guid uiContext, Action action); +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index 18ca571fa1314..425cfce0e740c 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -6,10 +6,10 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.VisualStudio.Shell; using Newtonsoft.Json; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -19,56 +19,71 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory +internal sealed class RazorDynamicRegistrationServiceFactory( + [Import(AllowDefault = true)] IUIContextActivationService? uIContextActivationService, + [Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new RazorDynamicRegistrationService(dynamicRegistrationService, clientLanguageServerManager); + return new RazorDynamicRegistrationService(uIContextActivationService, dynamicRegistrationService, clientLanguageServerManager); } - private class RazorDynamicRegistrationService : ILspService, IOnInitialized + private class RazorDynamicRegistrationService( + IUIContextActivationService? uIContextActivationService, + Lazy? dynamicRegistrationService, + IClientLanguageServerManager? clientLanguageServerManager) : ILspService, IOnInitialized, IDisposable { - private readonly Lazy? _dynamicRegistrationService; - private readonly IClientLanguageServerManager? _clientLanguageServerManager; - private readonly JsonSerializerSettings _serializerSettings; + private readonly CancellationTokenSource _disposalTokenSource = new(); - public RazorDynamicRegistrationService(Lazy? dynamicRegistrationService, IClientLanguageServerManager? clientLanguageServerManager) + public void Dispose() { - _dynamicRegistrationService = dynamicRegistrationService; - _clientLanguageServerManager = clientLanguageServerManager; - - var serializer = new JsonSerializer(); - serializer.AddVSInternalExtensionConverters(); - _serializerSettings = new JsonSerializerSettings { Converters = serializer.Converters }; + _disposalTokenSource.Cancel(); } public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - if (_dynamicRegistrationService is null || _clientLanguageServerManager is null) + if (dynamicRegistrationService is null || clientLanguageServerManager is null) { return Task.CompletedTask; } - var uiContext = UIContext.FromUIContextGuid(Constants.RazorCohostingUIContext); - uiContext.WhenActivated(() => + if (uIContextActivationService is null) { - // Not using the cancellation token passed in, as the context could be activated well after LSP server initialization - InitializeRazor(clientCapabilities, context, CancellationToken.None); - }); + // Outside of VS, we want to initialize immediately.. I think? + InitializeRazor(); + } + else + { + uIContextActivationService.ExecuteWhenActivated(Constants.RazorCohostingUIContext, InitializeRazor); + } return Task.CompletedTask; + + void InitializeRazor() + { + this.InitializeRazor(clientCapabilities, context, _disposalTokenSource.Token); + } } private void InitializeRazor(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { + // The LSP server will dispose us when the server exits, but VS could decide to activate us later. + // If a new instance of the server is created, a new instance of this class will be created and the + // UIContext will already be active, so this method will be immediately called on the new instance. + if (cancellationToken.IsCancellationRequested) return; + + var serializer = new JsonSerializer(); + serializer.AddVSInternalExtensionConverters(); + var serializerSettings = new JsonSerializerSettings { Converters = serializer.Converters }; + // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL - var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, _serializerSettings); - var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager!); + var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, serializerSettings); + var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(clientLanguageServerManager!); var requestContext = new RazorCohostRequestContext(context); - _dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); + dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs new file mode 100644 index 0000000000000..d04d43a5cd83c --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation; + +[Export(typeof(IUIContextActivationService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioUIContextActivationService() : IUIContextActivationService +{ + public void ExecuteWhenActivated(Guid uiContext, Action action) + { + var context = UIContext.FromUIContextGuid(uiContext); + context.WhenActivated(action); + } +} From a9b690f08634c10b1148102b1eaf49d44d505cfc Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 23 Apr 2024 10:38:45 +0200 Subject: [PATCH 0790/1047] Visit binary operators in ref safety analysis (#73081) * Add tests * Visit binary operators in ref safety analysis * Simplify visit tracking --- .../Portable/Binder/RefSafetyAnalysis.cs | 32 ++++++++------ .../Portable/BoundTree/BoundTreeWalker.cs | 13 ++++++ .../Semantic/Semantics/RefEscapingTests.cs | 42 +++++++++++++++++++ 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs index 2029d8d5aa669..04ac043860e7d 100644 --- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs +++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs @@ -258,6 +258,24 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) public override BoundNode? Visit(BoundNode? node) { #if DEBUG + TrackVisit(node); +#endif + return base.Visit(node); + } + +#if DEBUG + protected override void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node) + { + TrackVisit(node); + } + + protected override void BeforeVisitingSkippedBoundCallChildren(BoundCall node) + { + TrackVisit(node); + } + + private void TrackVisit(BoundNode? node) + { if (node is BoundValuePlaceholderBase placeholder) { Debug.Assert(ContainsPlaceholderScope(placeholder)); @@ -267,14 +285,11 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) if (_visited is { } && _visited.Count <= MaxTrackVisited) { bool added = _visited.Add(expr); - Debug.Assert(added); + Debug.Assert(added, $"Expression {expr} `{expr.Syntax}` visited more than once."); } } -#endif - return base.Visit(node); } -#if DEBUG private void AssertVisited(BoundExpression expr) { if (expr is BoundValuePlaceholderBase placeholder) @@ -283,7 +298,7 @@ private void AssertVisited(BoundExpression expr) } else if (_visited is { } && _visited.Count <= MaxTrackVisited) { - Debug.Assert(_visited.Contains(expr)); + Debug.Assert(_visited.Contains(expr), $"Expected {expr} `{expr.Syntax}` to be visited."); } } #endif @@ -659,13 +674,6 @@ protected override void VisitArguments(BoundCall node) _localScopeDepth, _diagnostics); } - -#if DEBUG - if (_visited is { } && _visited.Count <= MaxTrackVisited) - { - _visited.Add(node); - } -#endif } private void GetInterpolatedStringPlaceholders( diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs index a8ed41a6f003c..8f0df23b1e56c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs @@ -114,6 +114,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator var binary = (BoundBinaryOperator)node.Left; + BeforeVisitingSkippedBoundBinaryOperatorChildren(binary); rightOperands.Push(binary.Right); BoundExpression current = binary.Left; @@ -121,6 +122,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator while (current.Kind == BoundKind.BinaryOperator) { binary = (BoundBinaryOperator)current; + BeforeVisitingSkippedBoundBinaryOperatorChildren(binary); rightOperands.Push(binary.Right); current = binary.Left; } @@ -136,6 +138,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator return null; } + protected virtual void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node) + { + } + public sealed override BoundNode? VisitCall(BoundCall node) { if (node.ReceiverOpt is BoundCall receiver1) @@ -147,10 +153,13 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator node = receiver1; while (node.ReceiverOpt is BoundCall receiver2) { + BeforeVisitingSkippedBoundCallChildren(node); calls.Push(node); node = receiver2; } + BeforeVisitingSkippedBoundCallChildren(node); + VisitReceiver(node); do @@ -170,6 +179,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator return null; } + protected virtual void BeforeVisitingSkippedBoundCallChildren(BoundCall node) + { + } + /// /// Called only for the first (in evaluation order) in the chain. /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 6da08b9784005..0cd3ee133280d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -7871,6 +7871,37 @@ ref struct S Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Nested() + { + var source = """ + class C + { + S M() + { + S s; + s = default(S) + 100 + 200; + return s; + } + } + + ref struct S + { + public static S operator+(S y, in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100").WithArguments("S.operator +(S, in int)", "x").WithLocation(6, 13), + // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100 + 200").WithArguments("S.operator +(S, in int)", "y").WithLocation(6, 13), + // (6,26): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 26)); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] public void UserDefinedBinaryOperator_RefStruct_Scoped_Left() { @@ -8531,5 +8562,16 @@ static R F2() // return new R(1) | new R(2); Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(18, 22)); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72873")] + public void Utf8Addition() + { + var code = """ + using System; + ReadOnlySpan x = "Hello"u8 + " "u8 + "World!"u8; + Console.WriteLine(x.Length); + """; + CreateCompilation(code, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } } } From ce99f63cf9392fe4612fefee68f65c0968bf999c Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 23 Apr 2024 10:49:15 +0200 Subject: [PATCH 0791/1047] Remove mention of removed option `bootstrapConfig` (#73174) --- docs/contributing/Bootstrap Builds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/Bootstrap Builds.md b/docs/contributing/Bootstrap Builds.md index 52e1a638cc877..128a4d59fe740 100644 --- a/docs/contributing/Bootstrap Builds.md +++ b/docs/contributing/Bootstrap Builds.md @@ -104,5 +104,5 @@ https://github.com/dotnet/roslyn/blob/d73d31cbccb9aa850f3582afb464b709fef88fd7/s Next just run the bootstrap build locally, wait for the `Debug.Assert` to trigger which pops up a dialog. From there you can attach to the VBCSCompiler process and debug through the problem ```cmd -> Build.cmd -bootstrap -bootstrapConfiguration Debug +> Build.cmd -bootstrap ``` From 26687e18643d66378614f03b6c6c8d1eb6381218 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 07:56:32 -0700 Subject: [PATCH 0792/1047] Port improvements --- .../Protocol/DefaultCapabilitiesProvider.cs | 10 -- .../AbstractDocumentPullDiagnosticHandler.cs | 15 ++- .../AbstractPullDiagnosticHandler.cs | 13 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 5 +- .../AggregatedDocumentDiagnosticSource.cs | 114 +++++++++--------- .../DiagnosticSourceManager.cs | 18 +-- .../IDiagnosticSourceManager.cs | 2 +- .../DocumentPullDiagnosticHandler.cs | 20 ++- .../PublicDocumentPullDiagnosticsHandler.cs | 29 ++--- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 24 +--- .../PublicWorkspacePullDiagnosticsHandler.cs | 10 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 14 ++- .../WorkspacePullDiagnosticHandler.cs | 11 +- 13 files changed, 122 insertions(+), 163 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 2b2b974ac5a7c..75876a8c924b0 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -120,16 +120,6 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) // Using VS server capabilities because we have our own custom client. capabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions { TriggerCharacters = ["'", "/", "\n"] }; - if (!supportsVsExtensions) - { - capabilities.DiagnosticOptions = new DiagnosticOptions - { - InterFileDependencies = true, - WorkDoneProgress = true, - WorkspaceDiagnostics = true, - }; - } - return capabilities; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index dc20144b584f1..243e31c1fe547 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -2,17 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CommonLanguageServerProtocol.Framework; using Roslyn.LanguageServer.Protocol; -using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentPullDiagnosticHandler( IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, + IDiagnosticSourceManager diagnosticSourceManager, IGlobalOptionService globalOptions) : AbstractPullDiagnosticHandler( diagnosticAnalyzerService, @@ -20,5 +24,12 @@ internal abstract class AbstractDocumentPullDiagnosticHandler where TDiagnosticsParams : IPartialResultParams { - public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + protected readonly IDiagnosticSourceManager DiagnosticSourceManager = diagnosticSourceManager; + + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + { + return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); + } + + public abstract TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index e0f1e9de55a38..53c4aeb7224b1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -67,8 +67,6 @@ protected AbstractPullDiagnosticHandler( GlobalOptions = globalOptions; } - protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; - /// /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. Also /// used so we can report which documents were removed and can have all their diagnostics cleared. @@ -79,7 +77,7 @@ protected AbstractPullDiagnosticHandler( /// Returns all the documents that should be processed in the desired order to process them in. /// protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( - TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken); + TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); /// /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. @@ -105,7 +103,7 @@ protected abstract ValueTask> GetOrderedDiagno /// protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); - protected abstract string? GetDiagnosticCategory(TDiagnosticsParams diagnosticsParams); + protected abstract string GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); /// /// Used by public workspace pull diagnostics to allow it to keep the connection open until @@ -133,9 +131,8 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT Contract.ThrowIfNull(context.Solution); var clientCapabilities = context.GetRequiredClientCapabilities(); - var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; - var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; - var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; + var category = GetRequestDiagnosticCategory(diagnosticsParams); + var handlerName = $"{this.GetType().Name}(category: {category})"; context.TraceInformation($"{handlerName} started getting diagnostics"); var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); @@ -159,7 +156,7 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the // last time we notified the client. Report back either to the client so they can update accordingly. var orderedSources = await GetOrderedDiagnosticSourcesAsync( - diagnosticsParams, context, cancellationToken).ConfigureAwait(false); + diagnosticsParams, category, context, cancellationToken).ConfigureAwait(false); context.TraceInformation($"Processing {orderedSources.Length} documents"); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 4e97b9b6231e0..95a40d3a70f1d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -51,10 +51,9 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { - var sourceName = GetDiagnosticCategory(diagnosticsParams); - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, false, cancellationToken); } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs index d12148e32eb62..6a1038dc34229 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs @@ -1,65 +1,65 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.PooledObjects; +//using System.Collections.Generic; +//using System.Collections.Immutable; +//using System.Diagnostics; +//using System.Threading; +//using System.Threading.Tasks; +//using Microsoft.CodeAnalysis.Diagnostics; +//using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +//using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +//namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -/// -/// Aggregates multiple source diagnostics -/// -internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource -{ - private readonly IDiagnosticSourceManager _diagnosticSourceManager; - private readonly string? _sourceName; +///// +///// Aggregates multiple source diagnostics +///// +//internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource +//{ +// private readonly IDiagnosticSourceManager _diagnosticSourceManager; +// private readonly string? _sourceName; - public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, - TextDocument document, string? sourceName) : base(document) - { - _diagnosticSourceManager = diagnosticSourceManager; - _sourceName = sourceName; - } +// public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, +// TextDocument document, string? sourceName) : base(document) +// { +// _diagnosticSourceManager = diagnosticSourceManager; +// _sourceName = sourceName; +// } - public override bool IsLiveSource() => true; +// public override bool IsLiveSource() => true; - public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var name in GetSourceNames()) - { - var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); - foreach (var source in sources) - { - Debug.Assert(source.IsLiveSource(), "All document sources should be live"); - var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); - diagnostics.AddRange(namedDiagnostics); - } - } +// public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) +// { +// using var _ = ArrayBuilder.GetInstance(out var diagnostics); +// foreach (var name in GetSourceNames()) +// { +// var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); +// foreach (var source in sources) +// { +// Debug.Assert(source.IsLiveSource(), "All document sources should be live"); +// var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); +// diagnostics.AddRange(namedDiagnostics); +// } +// } - return diagnostics.ToImmutableAndClear(); - } +// return diagnostics.ToImmutableAndClear(); +// } - private IEnumerable GetSourceNames() - { - if (this._sourceName != null) - { - yield return this._sourceName; - } - else - { - foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) - { - if (name != PullDiagnosticCategories.Task) - yield return name; - } - } - } -} +// private IEnumerable GetSourceNames() +// { +// if (this._sourceName != null) +// { +// yield return this._sourceName; +// } +// else +// { +// foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) +// { +// if (name != PullDiagnosticCategories.Task) +// yield return name; +// } +// } +// } +//} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 448332f73132e..ac68762bfc7af 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -39,20 +39,10 @@ public IEnumerable GetSourceNames(bool isDocument) => (isDocument ? _documentProviders : _workspaceProviders).Keys; /// - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken) + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - if (sourceName != null) - { - if (providersDictionary.TryGetValue(sourceName, out var provider)) - return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); - } - else if (isDocument) - { - if (context.TextDocument is { } document) - return new([new AggregatedDocumentDiagnosticSource(this, document, null)]); - } - - return new([]); + Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); + return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs index b7849dac006e1..3246e5cc9153c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -27,6 +27,6 @@ internal interface IDiagnosticSourceManager /// Source name. /// True for document sources and false for workspace sources. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index e037834c4bc80..50d97de2263e6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -3,12 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { @@ -16,19 +15,20 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics internal partial class DocumentPullDiagnosticHandler : AbstractDocumentPullDiagnosticHandler { - private readonly IDiagnosticSourceManager _diagnosticSourceManager; public DocumentPullDiagnosticHandler( IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, globalOptions) + : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) { - _diagnosticSourceManager = diagnosticSourceManager; } - protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; + protected override string GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); + return diagnosticsParams.QueryingDiagnosticKind.Value.Value; + } public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.TextDocument; @@ -71,12 +71,6 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) - { - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, diagnosticsParams.QueryingDiagnosticKind?.Value, true, cancellationToken); - } - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) { return progress.GetFlattenedValues(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 19db1af4f9add..5149f0ece8f81 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -23,7 +24,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { private readonly IClientLanguageServerManager _clientLanguageServerManager; - private readonly IDiagnosticSourceManager _diagnosticSourceManager; public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, @@ -31,22 +31,19 @@ public PublicDocumentPullDiagnosticsHandler( IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, globalOptions) + : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) { - _diagnosticSourceManager = diagnosticSourceManager; _clientLanguageServerManager = clientLanguageServerManager; } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) - => null; + protected override string GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); + return diagnosticsParams.Identifier; + } public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; - protected override string? GetDiagnosticSourceIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); @@ -90,18 +87,6 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi return null; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) - { - if (context.TextDocument is { } document) - { - // Wrap all sources into ISourceProvider so that we can keep using DocumentDiagnosticReport - // (which supports a single source) - return new([new AggregatedDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); - } - - return new([]); - } - protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) { if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 2a52101e93329..407710a01b634 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,19 +18,17 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType; -internal sealed partial class PublicDocumentPullDiagnosticsHandler// : AbstractDocumentPullDiagnosticHandler, IOnInitialized +internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitialized { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - // Dynamically register a non-local document diagnostic source if Full solution background analysis is enabled - // for analyzer execution. This diagnostic source reports diagnostics in open documents that are reported - // when analyzing other documents or at compilation end. - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) + // Dynamically register for all of our document diagnostic sources. + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. - var sources = _diagnosticSourceManager.GetSourceNames(isDocument: true); + var sources = DiagnosticSourceManager.GetSourceNames(isDocument: true).Where(source => source != PullDiagnosticCategories.Task); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() @@ -42,20 +41,9 @@ await _clientLanguageServerManager.SendRequestAsync( Registration FromSourceName(string sourceName) => new() { - Id = sourceName, + Id = Guid.NewGuid().ToString(), Method = Methods.TextDocumentDiagnosticName, RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } }; - - bool IsFsaEnabled() - { - foreach (var language in context.SupportedLanguages) - { - if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) - return true; - } - - return false; - } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 013182d9e5bc3..33a380698f57e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -36,11 +36,11 @@ public PublicWorkspacePullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) - => null; + protected override string GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); + return diagnosticsParams.Identifier; + } protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index ed55a6164df3f..478ea62423dbe 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -21,19 +21,21 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { Registrations = sources.Select(FromSourceName).ToArray() }; - regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics + //regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: regParams, cancellationToken).ConfigureAwait(false); Registration FromSourceName(string sourceName) - => new() { - Id = sourceName, - Method = Methods.WorkspaceDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } - }; + return new() + { + Id = sourceName, + Method = Methods.WorkspaceDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkDoneProgress = true } + }; + } } bool IsFsaEnabled() diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 63e9e83e53495..9874e13c8a539 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -24,10 +24,13 @@ internal sealed partial class WorkspacePullDiagnosticHandler( : AbstractWorkspacePullDiagnosticsHandler( workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { - protected override string? GetDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; + protected override string GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); + return diagnosticsParams.QueryingDiagnosticKind.Value.Value; + } -protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) => [ new VSInternalWorkspaceDiagnosticReport { @@ -67,4 +70,4 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bo } internal override TestAccessor GetTestAccessor() => new(this); -} \ No newline at end of file +} From 66a86a274a7a979b9fe6fabea0ba472de063f6e0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 08:18:53 -0700 Subject: [PATCH 0793/1047] Remove specialized list pool. --- .../Core/Portable/Serialization/PooledList.cs | 19 ------------------- .../SerializerService_Reference.cs | 13 +++++++------ 2 files changed, 7 insertions(+), 25 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Serialization/PooledList.cs diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs deleted file mode 100644 index 3eb2680bb6702..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.Serialization; - -/// -/// This is just internal utility type to reduce allocations and redundant code -/// -internal static class Creator -{ - public static PooledObject> CreateList() - => SharedPools.Default>().GetPooledObject(); -} diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index d81de69768748..031ce659fd857 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -342,10 +342,11 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var metadataKind = (MetadataImageKind)imageKind; if (metadataKind == MetadataImageKind.Assembly) { - using var pooledMetadata = Creator.CreateList(); - using var pooledHandles = Creator.CreateList(); - var count = reader.ReadInt32(); + + var allMetadata = new FixedSizeArrayBuilder(count); + var allHandles = new FixedSizeArrayBuilder(count); + for (var i = 0; i < count; i++) { metadataKind = (MetadataImageKind)reader.ReadInt32(); @@ -353,11 +354,11 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var (metadata, storageHandle) = ReadModuleMetadataFrom(reader, kind, cancellationToken); - pooledMetadata.Object.Add(metadata); - pooledHandles.Object.Add(storageHandle); + allMetadata.Add(metadata); + allHandles.Add(storageHandle); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledHandles.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(allMetadata.MoveToImmutable()), allHandles.MoveToImmutable()); } else { From b34e8631a319ae680fe5d48daaf77fcc5f13e179 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 09:18:12 -0700 Subject: [PATCH 0794/1047] Restore support for null categories --- .../AbstractDocumentPullDiagnosticHandler.cs | 2 +- .../AbstractPullDiagnosticHandler.cs | 4 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 2 +- .../AggregatedDocumentDiagnosticSource.cs | 65 ------------------- .../DiagnosticSourceManager.cs | 61 ++++++++++++++++- .../IDiagnosticSourceManager.cs | 2 +- .../DocumentPullDiagnosticHandler.cs | 8 +-- .../PublicDocumentPullDiagnosticsHandler.cs | 10 +-- .../PublicWorkspacePullDiagnosticsHandler.cs | 7 +- .../WorkspacePullDiagnosticHandler.cs | 7 +- 10 files changed, 71 insertions(+), 97 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 243e31c1fe547..648dd3e0dc428 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -26,7 +26,7 @@ internal abstract class AbstractDocumentPullDiagnosticHandler> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 53c4aeb7224b1..85177e1a7fec9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -77,7 +77,7 @@ protected AbstractPullDiagnosticHandler( /// Returns all the documents that should be processed in the desired order to process them in. /// protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( - TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); + TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); /// /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. @@ -103,7 +103,7 @@ protected abstract ValueTask> GetOrderedDiagno /// protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); - protected abstract string GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); + protected abstract string? GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); /// /// Used by public workspace pull diagnostics to allow it to keep the connection open until diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 95a40d3a70f1d..6b6fee7538432 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -51,7 +51,7 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, false, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs deleted file mode 100644 index 6a1038dc34229..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs +++ /dev/null @@ -1,65 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System.Collections.Generic; -//using System.Collections.Immutable; -//using System.Diagnostics; -//using System.Threading; -//using System.Threading.Tasks; -//using Microsoft.CodeAnalysis.Diagnostics; -//using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -//using Microsoft.CodeAnalysis.PooledObjects; - -//namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -///// -///// Aggregates multiple source diagnostics -///// -//internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource -//{ -// private readonly IDiagnosticSourceManager _diagnosticSourceManager; -// private readonly string? _sourceName; - -// public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, -// TextDocument document, string? sourceName) : base(document) -// { -// _diagnosticSourceManager = diagnosticSourceManager; -// _sourceName = sourceName; -// } - -// public override bool IsLiveSource() => true; - -// public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) -// { -// using var _ = ArrayBuilder.GetInstance(out var diagnostics); -// foreach (var name in GetSourceNames()) -// { -// var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); -// foreach (var source in sources) -// { -// Debug.Assert(source.IsLiveSource(), "All document sources should be live"); -// var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); -// diagnostics.AddRange(namedDiagnostics); -// } -// } - -// return diagnostics.ToImmutableAndClear(); -// } - -// private IEnumerable GetSourceNames() -// { -// if (this._sourceName != null) -// { -// yield return this._sourceName; -// } -// else -// { -// foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) -// { -// if (name != PullDiagnosticCategories.Task) -// yield return name; -// } -// } -// } -//} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index ac68762bfc7af..0482e5c8b9144 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -6,11 +6,15 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -39,10 +43,61 @@ public IEnumerable GetSourceNames(bool isDocument) => (isDocument ? _documentProviders : _workspaceProviders).Keys; /// - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken) { var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); - return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); + if (sourceName != null) + { + Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); + return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + } + else + { + // VS Code (and legacy VS ?) pass null sourceName when requesting all sources. + using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); + foreach (var kvp in providersDictionary) + { + // Exclude task diagnostics from the aggregated sources. + if (kvp.Key != PullDiagnosticCategories.Task) + { + var namedSources = await kvp.Value.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + sourcesBuilder.AddRange(namedSources); + } + } + + var sources = sourcesBuilder.ToImmutableAndClear(); + if (!isDocument || sources.Length <= 1) + { + return sources; + } + else + { + // VS Code document handler (and legacy VS ?) expects a single source for document diagnostics. + // For more details see PublicDocumentPullDiagnosticsHandler.CreateReturn. + Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); + return [new AggregatedDocumentDiagnosticSource(sources)]; + } + } + } + + private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + { + public bool IsLiveSource() => true; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + foreach (var source in sources) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + + return diagnostics.ToImmutableAndClear(); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs index 3246e5cc9153c..b7849dac006e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -27,6 +27,6 @@ internal interface IDiagnosticSourceManager /// Source name. /// True for document sources and false for workspace sources. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 50d97de2263e6..f71ed92ae0fbd 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { @@ -24,11 +23,8 @@ public DocumentPullDiagnosticHandler( { } - protected override string GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); - return diagnosticsParams.QueryingDiagnosticKind.Value.Value; - } + protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.TextDocument; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 5149f0ece8f81..85ffa72472435 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -4,13 +4,10 @@ using System.Collections.Immutable; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -36,11 +33,8 @@ public PublicDocumentPullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - protected override string GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); - return diagnosticsParams.Identifier; - } + protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 33a380698f57e..545964f7b2a1a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -36,11 +36,8 @@ public PublicWorkspacePullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - protected override string GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); - return diagnosticsParams.Identifier; - } + protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 9874e13c8a539..e9258826e863f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -24,11 +24,8 @@ internal sealed partial class WorkspacePullDiagnosticHandler( : AbstractWorkspacePullDiagnosticsHandler( workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { - protected override string GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); - return diagnosticsParams.QueryingDiagnosticKind.Value.Value; - } + protected override string? GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) => [ From 4244eec1b736800cc408f8c3c7ffebf372de0d79 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 09:47:12 -0700 Subject: [PATCH 0795/1047] In progress --- .../Diagnostics/IDiagnosticAnalyzerService.cs | 40 ++++++++++--------- .../Diagnostics/DiagnosticAnalyzerService.cs | 14 ++++++- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 4 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 21 ++++++++-- ...stractWorkspaceDocumentDiagnosticSource.cs | 13 +++--- .../DocumentDiagnosticSource.cs | 6 +-- .../Test.Next/Services/AssetProviderTests.cs | 4 +- .../Services/ServiceHubServicesTests.cs | 2 +- .../Workspace/Solution/StateChecksums.cs | 6 +++ .../Host/RemoteWorkspace.SolutionCreator.cs | 2 +- .../Remote/ServiceHub/Host/TestUtils.cs | 25 ++++++------ 11 files changed, 86 insertions(+), 51 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index a1521848a186c..ecc3404ea591d 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -69,9 +69,11 @@ internal interface IDiagnosticAnalyzerService Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); /// - /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that for project case, this method returns diagnostics from all project documents as well. Use - /// if you want to fetch only project diagnostics without source locations. + /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned + /// should be up-to-date with respect to the given solution. Note that for project case, this method returns + /// diagnostics from all project documents as well. Use if you + /// want to fetch only project diagnostics without source locations. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -80,22 +82,25 @@ internal interface IDiagnosticAnalyzerService /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. /// - /// Indicates if local document diagnostics must be returned. - /// Local diagnostics are the ones that are reported by analyzers on the same file for which the callback was received - /// and hence can be computed by analyzing a single file in isolation. + /// Indicates if local document diagnostics must be returned. Local diagnostics are the ones that are reported by + /// analyzers on the same file for which the callback was received and hence can be computed by analyzing a single + /// file in isolation. /// /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR - /// in a different file from which the callback was made. Entire project must be analyzed to get the - /// complete set of non-local document diagnostics. + /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by + /// analyzers either at compilation end callback OR in a different file from which the callback was made. Entire + /// project must be analyzed to get the complete set of non-local document diagnostics. /// - /// Cancellation token. - Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetDiagnosticsForIdsAsync( + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, + bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that this method doesn't return any document diagnostics. Use to also fetch those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from + /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that + /// this method doesn't return any document diagnostics. Use to also fetch + /// those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -103,11 +108,10 @@ internal interface IDiagnosticAnalyzerService /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via SuppressMessageAttributes should be returned. /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback. - /// Entire project must be analyzed to get the complete set of non-local diagnostics. + /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by + /// analyzers either at compilation end callback. Entire project must be analyzed to get the complete set of + /// non-local diagnostics. /// - /// Cancellation token. Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index aac07fce0e0f2..1739673da3dbf 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -134,10 +134,20 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Solution solution, + ProjectId? projectId, + DocumentId? documentId, + ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, + DiagnosticKind diagnosticKind, + bool includeSuppressedDiagnostics, + bool includeLocalDocumentDiagnostics, + bool includeNonLocalDocumentDiagnostics, + CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync( + solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index cb1dc8c68d0b9..1fad5792bf29d 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -21,8 +21,8 @@ public Task> GetCachedDiagnosticsAsync(Solution s public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index f3911586aad23..b056a182602f3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -78,7 +78,7 @@ protected override async ValueTask> GetOrdered // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); + return await GetDiagnosticSourcesAsync(context, GlobalOptions, category, cancellationToken).ConfigureAwait(false); // if it's a category we don't recognize, return nothing. return []; @@ -158,10 +158,25 @@ private static ImmutableArray GetTaskListDiagnosticSources( /// we have no workspace diagnostics to report and bail out. /// public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) + RequestContext context, IGlobalOptionService globalOptions, string category, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); + var diagnosticKind = category switch + { + PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, + // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). + null => DiagnosticKind.All, + // if it's a category we don't recognize, return nothing. + _ => (DiagnosticKind?)null, + }; + + if (diagnosticKind is null) + return []; + using var _ = ArrayBuilder.GetInstance(out var result); var solution = context.Solution; @@ -205,7 +220,7 @@ void AddDocumentSources(IEnumerable documents) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticKind.Value, shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); result.Add(documentDiagnosticSource); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..7187c68e4cfe9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -12,13 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, diagnosticKind, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -35,7 +35,8 @@ public override async Task> GetDiagnosticsAsync( if (Document is SourceGeneratedDocument sourceGeneratedDocument) { // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(sourceGeneratedDocument, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( + sourceGeneratedDocument, range: null, diagnosticKind, includeSuppressedDiagnostics: false, cancellationToken).ConfigureAwait(false); return documentDiagnostics; } else @@ -45,8 +46,8 @@ public override async Task> GetDiagnosticsAsync( // However we can include them as a part of workspace pull when FSA is on. var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + diagnosticIds: null, shouldIncludeAnalyzer, diagnosticKind, + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); return documentDiagnostics; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 54f5c6ce5deb2..50e3e06811c1f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -14,8 +14,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; - /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -30,11 +28,11 @@ public override async Task> GetDiagnosticsAsync( // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 - if (DiagnosticKind == DiagnosticKind.AnalyzerSemantic) + if (diagnosticKind == DiagnosticKind.AnalyzerSemantic) { var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(span: null, cancellationToken).ConfigureAwait(false); allSpanDiagnostics = allSpanDiagnostics.AddRange(copilotDiagnostics); diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index 30732fa083aa0..f7424abe817b4 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -76,7 +76,7 @@ public async Task TestAssetSynchronization() // build checksum await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); using var remoteWorkspace = CreateRemoteWorkspace(); @@ -104,7 +104,7 @@ public async Task TestSolutionSynchronization() // build checksum await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); using var remoteWorkspace = CreateRemoteWorkspace(); diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 8ec086b01c673..d73b4bb4f6ee9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -1450,7 +1450,7 @@ private static void VerifyStates(Solution solution1, Solution solution2, string private static async Task VerifyAssetStorageAsync(InProcRemoteHostClient client, Solution solution) { - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); var storage = client.TestData.WorkspaceManager.SolutionAssetCache; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index dd1c5d79ec793..ccd0717320450 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,6 +486,9 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } + + public override string ToString() + => $"ProjectStateChecksums({ProjectId})"; } internal sealed class DocumentStateChecksums( @@ -526,6 +529,9 @@ public async Task FindAsync( onAssetFound(Text, text, arg); } } + + public override string ToString() + => $"DocumentStateChecksums({DocumentId})"; } /// diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5a44050959030..5c23f42a6faa0 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -685,7 +685,7 @@ private async Task ValidateChecksumAsync( var workspace = new AdhocWorkspace(_hostServices); workspace.AddSolution(solutionInfo); - await TestUtils.AssertChecksumsAsync(_assetProvider, checksumFromRequest, workspace.CurrentSolution, incrementalSolutionBuilt).ConfigureAwait(false); + await TestUtils.AssertChecksumsAsync(_assetProvider, checksumFromRequest, workspace.CurrentSolution, incrementalSolutionBuilt, projectConeId).ConfigureAwait(false); } #endif } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 27047d2158e5b..ada4f5f1be1c1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -40,43 +40,44 @@ internal static async Task AssertChecksumsAsync( AssetProvider assetService, Checksum checksumFromRequest, Solution solutionFromScratch, - Solution incrementalSolutionBuilt) + Solution incrementalSolutionBuilt, + ProjectId? projectConeId) { #if DEBUG var sb = new StringBuilder(); var allChecksumsFromRequest = await GetAllChildrenChecksumsAsync(checksumFromRequest).ConfigureAwait(false); - var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); - var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); + var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(projectConeId, CancellationToken.None).ConfigureAwait(false); + var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(projectConeId, CancellationToken.None).ConfigureAwait(false); // check 4 things // 1. first see if we create new solution from scratch, it works as expected (indicating a bug in incremental update) var mismatch1 = assetMapFromNewSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); - AppendMismatch(mismatch1, "assets only in new solutoin but not in the request", sb); + AppendMismatch(mismatch1, "Assets only in new solution but not in the request", sb); // 2. second check what items is mismatching for incremental solution var mismatch2 = assetMapFromIncrementalSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); - AppendMismatch(mismatch2, "assets only in the incremental solution but not in the request", sb); + AppendMismatch(mismatch2, "Assets only in the incremental solution but not in the request", sb); // 3. check whether solution created from scratch and incremental one have any mismatch var mismatch3 = assetMapFromNewSolution.Where(p => !assetMapFromIncrementalSolution.ContainsKey(p.Key)).ToList(); - AppendMismatch(mismatch3, "assets only in new solution but not in incremental solution", sb); + AppendMismatch(mismatch3, "Assets only in new solution but not in incremental solution", sb); var mismatch4 = assetMapFromIncrementalSolution.Where(p => !assetMapFromNewSolution.ContainsKey(p.Key)).ToList(); - AppendMismatch(mismatch4, "assets only in incremental solution but not in new solution", sb); + AppendMismatch(mismatch4, "Assets only in incremental solution but not in new solution", sb); // 4. see what item is missing from request var mismatch5 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromNewSolution.Keys)).ConfigureAwait(false); - AppendMismatch(mismatch5, "assets only in the request but not in new solution", sb); + AppendMismatch(mismatch5, "Assets only in the request but not in new solution", sb); var mismatch6 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromIncrementalSolution.Keys)).ConfigureAwait(false); - AppendMismatch(mismatch6, "assets only in the request but not in incremental solution", sb); + AppendMismatch(mismatch6, "Assets only in the request but not in incremental solution", sb); var result = sb.ToString(); if (result.Length > 0) { Logger.Log(FunctionId.SolutionCreator_AssetDifferences, result); - Debug.Fail("Differences detected in solution checksum: " + result); + Debug.Fail($"Differences detected in solution checksum (ProjectId={projectConeId}):\r\n{result}"); } return; @@ -154,10 +155,10 @@ private static void AddAllTo(DocumentStateChecksums documentStateChecksums, Hash /// create checksum to corresponding object map from solution this map should contain every parts of solution /// that can be used to re-create the solution back /// - public static async Task> GetAssetMapAsync(this Solution solution, CancellationToken cancellationToken) + public static async Task> GetAssetMapAsync(this Solution solution, ProjectId? projectConeId, CancellationToken cancellationToken) { var map = new Dictionary(); - await solution.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); + await solution.AppendAssetMapAsync(map, projectConeId, cancellationToken).ConfigureAwait(false); return map; } From 6da89112795daab4deaca6deaec64b7a13568c5e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 09:55:47 -0700 Subject: [PATCH 0796/1047] Remove diagnostic function not used in any product code --- .../Diagnostics/IDiagnosticAnalyzerService.cs | 16 ---------------- .../Diagnostics/DiagnosticAnalyzerService.cs | 6 ------ ...agnosticIncrementalAnalyzer_GetDiagnostics.cs | 3 --- .../Diagnostics/TestDiagnosticAnalyzerDriver.cs | 8 ++++++-- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index a1521848a186c..f2099d71bf024 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -47,22 +47,6 @@ internal interface IDiagnosticAnalyzerService /// Cancellation token. Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - /// - /// Get diagnostics for the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// - /// Solution to fetch diagnostics for. - /// Optional project to scope the returned diagnostics. - /// Optional document to scope the returned diagnostics. - /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. - /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR - /// in a different file from which the callback was made. Entire project must be analyzed to get the - /// complete set of non-local document diagnostics. - /// - /// Cancellation token. - Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - /// /// Force analyzes the given project by running all applicable analyzers on the project and caching the reported analyzer diagnostics. /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index aac07fce0e0f2..d2d7ff6d55715 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -121,12 +121,6 @@ public Task> GetCachedDiagnosticsAsync(Workspace return analyzer.GetCachedDiagnosticsAsync(workspace.CurrentSolution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - { - var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); - } - public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index cb1dc8c68d0b9..8df2a61cd58f2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -18,9 +18,6 @@ internal partial class DiagnosticIncrementalAnalyzer public Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeCachedDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); diff --git a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 8419a40314bd9..44f697772dedd 100644 --- a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -55,7 +55,9 @@ private async Task> GetDiagnosticsAsync( if (getDocumentDiagnostics) { var text = await document.GetTextAsync().ConfigureAwait(false); - var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, document.Id, _includeSuppressedDiagnostics, _includeNonLocalDocumentDiagnostics, CancellationToken.None); + var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync( filterSpan is null ? dxs.Where(d => d.DataLocation.DocumentId != null) @@ -66,7 +68,9 @@ filterSpan is null if (getProjectDiagnostics) { - var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, documentId: null, _includeSuppressedDiagnostics, _includeNonLocalDocumentDiagnostics, CancellationToken.None); + var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); projectDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.DocumentId is null), project, CancellationToken.None); } From 00a567d58beb10bd87e0a5de68f2f265bc6584ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:04:24 -0700 Subject: [PATCH 0797/1047] in progress --- .../InlineDiagnosticsTaggerProviderTests.cs | 2 +- .../Test/CodeFixes/CodeFixServiceTests.cs | 5 ++- .../DiagnosticAnalyzerServiceTests.cs | 5 ++- .../Diagnostics/DiagnosticProviderTests.vb | 8 ++-- .../Diagnostics/DiagnosticServiceTests.vb | 42 ++++++++----------- .../Squiggles/SquiggleUtilities.cs | 8 +--- .../Squiggles/TestDiagnosticTagProducer.cs | 4 +- .../ExternalDiagnosticUpdateSourceTests.vb | 4 -- 8 files changed, 32 insertions(+), 46 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs index 339bf5c7e32b2..8462f9b447d79 100644 --- a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs @@ -55,6 +55,6 @@ private static async Task>> GetTag private static async Task>> GetTagSpansAsync(EditorTestWorkspace workspace) { workspace.GlobalOptions.SetGlobalOption(InlineDiagnosticsOptionsStorage.EnableInlineDiagnostics, LanguageNames.CSharp, true); - return (await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace)).Item2; + return await TestDiagnosticTagProducer.GetTagSpansAsync(workspace); } } diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 7cafd90ce14eb..46fd1f86dcc0e 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1079,8 +1079,9 @@ void M() ? root.DescendantNodes().OfType().First().Span : root.DescendantNodes().OfType().First().Span; - await diagnosticIncrementalAnalyzer.GetDiagnosticsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, includeSuppressedDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); + await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); var lowPriorityAnalyzers = new ConcurrentSet(); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 9fb93970e27cf..58c1dce218eb7 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -71,8 +71,9 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var analyzer = service.CreateIncrementalAnalyzer(workspace); var globalOptions = exportProvider.GetExportedValue(); - var diagnostics = await analyzer.GetDiagnosticsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, includeSuppressedDiagnostics: false, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); + var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index 479f3b4014a6b..df768f0d7279d 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -261,11 +261,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Next Dim diagnosticProvider = GetDiagnosticProvider(workspace) - Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None).Result + Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result If diagnostics Is Nothing Then Assert.Equal(0, actualDiagnostics.Length) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 1f9aec9da2ced..307c1437ddec0 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -536,10 +536,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, root.FullSpan) Assert.Equal(0, diagnostics.Count()) - diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, projectId:=Nothing, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None).ConfigureAwait(False) + diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Dim diagnostic = diagnostics.First() Assert.True(diagnostic.Id = "AD0001") Assert.Contains("CodeBlockStartedAnalyzer", diagnostic.Message, StringComparison.Ordinal) @@ -608,10 +607,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, diagnostics.Count()) Dim diagnostic = diagnostics.First() Assert.Equal(OperationAnalyzer.Descriptor.Id, diagnostic.Id) @@ -951,10 +949,9 @@ class AnonymousFunctions ' Verify no duplicate analysis/diagnostics. Dim document = project.Documents.Single() - Dim diagnostics = (Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None)). + Dim diagnostics = (Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None)). Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id) Assert.Equal(1, diagnostics.Count) @@ -1047,10 +1044,9 @@ class AnonymousFunctions ' Verify project diagnostics contains diagnostics reported on both partial definitions. Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, diagnostics.Count()) Dim file1HasDiag = False, file2HasDiag = False For Each diagnostic In diagnostics @@ -2151,10 +2147,9 @@ class MyClass Assert.Equal(expectedCount, diagnostics.Count()) ' Get diagnostics explicitly - Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, hiddenDiagnostics.Count()) Assert.Equal(analyzer.Descriptor.Id, hiddenDiagnostics.Single().Id) End Using @@ -2239,10 +2234,9 @@ class C Assert.Equal(1, descriptorsMap.Count) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(0, diagnostics.Count()) End Using End Function diff --git a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs index 1a76d41a4f352..90ed3e6ea22e0 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs @@ -28,7 +28,7 @@ public static class SquiggleUtilities internal static TestComposition WpfCompositionWithSolutionCrawler = EditorTestCompositions.EditorFeaturesWpf .RemoveParts(typeof(MockWorkspaceEventListenerProvider)); - internal static async Task<(ImmutableArray, ImmutableArray>)> GetDiagnosticsAndErrorSpansAsync( + internal static async Task>> GetTagSpansAsync( EditorTestWorkspace workspace, IReadOnlyDictionary> analyzerMap = null) where TProvider : AbstractDiagnosticsTaggerProvider @@ -43,14 +43,10 @@ public static class SquiggleUtilities using var disposable = tagger as IDisposable; await wrapper.WaitForTags(); - var service = (DiagnosticAnalyzerService)workspace.ExportProvider.GetExportedValue(); - var analyzerDiagnostics = await service.GetDiagnosticsAsync(workspace.CurrentSolution, - projectId: null, documentId: null, includeSuppressedDiagnostics: false, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); - var snapshot = textBuffer.CurrentSnapshot; var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToImmutableArray(); - return (analyzerDiagnostics, spans); + return spans; } } } diff --git a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs index 79108121d640b..d42a8b5abcc4d 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs @@ -18,11 +18,11 @@ internal sealed class TestDiagnosticTagProducer where TProvider : AbstractDiagnosticsTaggerProvider where TTag : class, ITag { - internal static Task<(ImmutableArray, ImmutableArray>)> GetDiagnosticsAndErrorSpans( + internal static Task>> GetTagSpansAsync( EditorTestWorkspace workspace, IReadOnlyDictionary>? analyzerMap = null) { - return SquiggleUtilities.GetDiagnosticsAndErrorSpansAsync(workspace, analyzerMap); + return SquiggleUtilities.GetTagSpansAsync(workspace, analyzerMap); } internal static DiagnosticData CreateDiagnosticData(EditorTestHostDocument document, TextSpan span) diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 0c278d44d7ece..7b9b26917d379 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -430,10 +430,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetDiagnosticsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, includeSuppressedDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsAsync - Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() - End Function - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function From 79df2f740f286eb81e3239a1d6bfdc40d6a3e8e8 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 10:06:14 -0700 Subject: [PATCH 0798/1047] clean up --- .../AlwaysActivateInProcLanguageClient.cs | 20 ---- .../Portable/Diagnostics/DiagnosticKind.cs | 1 - ...stractWorkspaceDiagnosticSourceProvider.cs | 1 - ...ExportDiagnosticSourceProviderAttribute.cs | 20 ---- .../DiagnosticSources/IDiagnosticSource.cs | 2 +- ...EditAndContinueDiagnosticSourceProvider.cs | 3 +- .../DocumentPullDiagnosticHandler.cs | 101 +++++++++--------- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 9 +- .../DocumentTaskDiagnosticSourceProvider.cs | 3 +- ...ocumentNonLocalDiagnosticSourceProvider.cs | 3 +- .../PublicDocumentPullDiagnosticsHandler.cs | 3 +- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 8 +- ...PublicWorkspaceDiagnosticSourceProvider.cs | 25 ----- ...cePullDiagnosticsHandler_IOnInitialized.cs | 1 - ...mentsAndProjectDiagnosticSourceProvider.cs | 3 +- ...EditAndContinueDiagnosticSourceProvider.cs | 3 +- .../WorkspaceTaskDiagnosticSourceProvider.cs | 3 +- .../Contracts/HotReloadDocumentDiagnostics.cs | 4 +- .../Contracts/IHotReloadDiagnosticManager.cs | 4 +- ...stractHotReloadDiagnosticSourceProvider.cs | 3 +- ...cumentHotReloadDiagnosticSourceProvider.cs | 4 +- .../Internal/HotReloadDiagnosticManager.cs | 8 +- ...kspaceHotReloadDiagnosticSourceProvider.cs | 3 +- .../InternalAPI.Unshipped.txt | 4 +- .../Xaml/Internal/XamlDiagnosticSource.cs | 3 +- .../Internal/XamlDiagnosticSourceProvider.cs | 5 +- 26 files changed, 78 insertions(+), 169 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 4ffb0938af9b0..8c8921a532a49 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -81,26 +81,6 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa { SupportsMultipleContextsDiagnostics = true, DiagnosticKinds = diagnosticSourceNames.Select(n => new VSInternalDiagnosticKind(n)).ToArray(), - //[ - // // Needs to be MEF driven - - // // Support a specialized requests dedicated to task-list items. This way the client can ask just - // // for these, independently of other diagnostics. They can also throttle themselves to not ask if - // // the task list would not be visible. - // new(PullDiagnosticCategories.Task), - // new(PullDiagnosticCategories.EditAndContinue), - // // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. - // new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - // // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic - // // requests, allowing the former to quickly reach the user without blocking on the latter. In a - // // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing - // // the former to appear as soon as possible as they are much more critical for the user and should - // // not be delayed by a slow analyzer. - // new(PullDiagnosticCategories.DocumentCompilerSyntax), - // new(PullDiagnosticCategories.DocumentCompilerSemantic), - // new(PullDiagnosticCategories.DocumentAnalyzerSyntax), - // new(PullDiagnosticCategories.DocumentAnalyzerSemantic), - //], BuildOnlyDiagnosticIds = _buildOnlyDiagnostics .SelectMany(lazy => lazy.Metadata.BuildOnlyDiagnostics) .Distinct() diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs index 92e43a3d0e51a..bc97619a79475 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs @@ -13,5 +13,4 @@ internal enum DiagnosticKind CompilerSemantic = 2, AnalyzerSyntax = 3, AnalyzerSemantic = 4, - EditAndContinue = 5, } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 473aecf7010d5..4a2312e0e198d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -71,4 +71,3 @@ protected static bool ShouldSkipDocument(RequestContext context, TextDocument do return document.IsRazorDocument(); } } - diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs deleted file mode 100644 index 2c165e3d7ca29..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; - -/// -/// Use this attribute to declare a implementation for inclusion in a MEF-based workspace. -/// -/// -/// Declares a implementation for inclusion in a MEF-based workspace. -/// -[MetadataAttribute] -[AttributeUsage(AttributeTargets.Class)] -internal class ExportDiagnosticSourceProviderAttribute() : ExportAttribute(typeof(IDiagnosticSourceProvider)) -{ -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs index 242be19b8d8b6..e8cb70bc1de4c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// Wrapper around a source for diagnostics (e.g. a or ) /// so that we can share per file diagnostic reporting code in /// -internal interface IDiagnosticSource // we'll need one for XHR errors +internal interface IDiagnosticSource { Project GetProject(); ProjectOrDocumentId GetId(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs index 52aa70dcab7a0..76e37b53b4d2b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -9,11 +9,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index f71ed92ae0fbd..16ee2f87d2bf2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -8,68 +8,67 @@ using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Method(VSInternalMethods.DocumentPullDiagnosticName)] +internal partial class DocumentPullDiagnosticHandler + : AbstractDocumentPullDiagnosticHandler { - [Method(VSInternalMethods.DocumentPullDiagnosticName)] - internal partial class DocumentPullDiagnosticHandler - : AbstractDocumentPullDiagnosticHandler + public DocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) - { - } - - protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; - - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.TextDocument; + } - protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalDiagnosticReport - { - Diagnostics = diagnostics, - ResultId = resultId, - Identifier = DocumentDiagnosticIdentifier, - // Mark these diagnostics as superseding any diagnostics for the same document from the - // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic - // values for a particular file, so our results should always be preferred over the workspace-pull - // values which are cached and may be out of date. - Supersedes = WorkspaceDiagnosticIdentifier, - } - ]; + protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.TextDocument; - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) - { - report = CreateReport(identifier, diagnostics: null, resultId); - return true; - } - - protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalDiagnosticReport { - return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); + Diagnostics = diagnostics, + ResultId = resultId, + Identifier = DocumentDiagnosticIdentifier, + // Mark these diagnostics as superseding any diagnostics for the same document from the + // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic + // values for a particular file, so our results should always be preferred over the workspace-pull + // values which are cached and may be out of date. + Supersedes = WorkspaceDiagnosticIdentifier, } + ]; - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - return null; - } + protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) { - return progress.GetFlattenedValues(); + return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); } + + // The client didn't provide us with a previous result to look for, so we can't lookup anything. + return null; + } + + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); + + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index a33f882167d5e..436465a8f351d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -34,7 +33,7 @@ public override ValueTask> CreateDiagnosticSou return new([source]); } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) @@ -43,7 +42,7 @@ internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] ID { } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) @@ -52,7 +51,7 @@ internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] { } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) @@ -61,7 +60,7 @@ internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] ID { } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs index 8a343fabb4899..c020ee7e30e81 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -8,12 +8,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 171fa9bb9bd55..876f058999c8d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -9,13 +9,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SolutionCrawler; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 85ffa72472435..5368045e550da 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -36,7 +36,8 @@ public PublicDocumentPullDiagnosticsHandler( protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; - public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; + public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 407710a01b634..f2a7244eeeec8 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -6,17 +6,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic -using DocumentDiagnosticPartialReport = SumType; internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitialized { @@ -29,11 +24,12 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // to dynamically register/unregister the non-local document diagnostic source. var sources = DiagnosticSourceManager.GetSourceNames(isDocument: true).Where(source => source != PullDiagnosticCategories.Task); + var registrations = sources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() { - Registrations = sources.Select(FromSourceName).ToArray() + Registrations = registrations }, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs deleted file mode 100644 index c7e98b6187713..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -// FIGURE OUT WHAT IT IS SUPPOSED TO DO -/* -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class PublicWorkspaceDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, - [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider("") -{ -} -*/ diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 478ea62423dbe..a87be284174f0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -21,7 +21,6 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { Registrations = sources.Select(FromSourceName).ToArray() }; - //regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: regParams, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 8c36d0905616c..38e94f79aceaa 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -18,7 +17,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs index 8869ef4ccccfa..cb8ff971b769b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -9,12 +9,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs index c5c18f4163c3f..32b09034eebe2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.TaskList; @@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs index 6f948ddfaef3d..13e899beb1c4c 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs @@ -6,9 +6,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts { - internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray errors) + internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray diagnostics) { public DocumentId DocumentId => documentId; - public ImmutableArray Errors => errors; + public ImmutableArray Diagnostics => diagnostics; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs index 3e601bb176a98..29d9562916cdc 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -14,12 +14,12 @@ internal interface IHotReloadDiagnosticManager ImmutableArray Sources { get; } /// - /// Registers source of hot reload diagnostics. + /// Registers source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. /// void Register(IHotReloadDiagnosticSource source); /// - /// Unregisters source of hot reload diagnostics. + /// Unregisters source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. /// void Unregister(IHotReloadDiagnosticSource source); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs index dcc71a2714646..832c1c6aa7c97 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -13,8 +13,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal abstract class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider { - string IDiagnosticSourceProvider.Name => "HotReloadDiagnostic"; - + string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) => throw new NotImplementedException(); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs index 16d9e109d4741..f568fffbc8a43 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -6,17 +6,15 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadDiagnosticManager) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index cdd278715f947..b045e419d3eb7 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -25,15 +25,9 @@ void IHotReloadDiagnosticManager.Register(IHotReloadDiagnosticSource source) { // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. if (!_sources.Contains(source)) - { _sources = _sources.Add(source); - } } void IHotReloadDiagnosticManager.Unregister(IHotReloadDiagnosticSource source) - { - // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. - _sources = _sources.Remove(source); - diagnosticsRefresher.RequestWorkspaceRefresh(); - } + => _sources = _sources.Remove(source); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index 9648b2902a8c3..c147997aac46d 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -12,12 +12,11 @@ using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 439b114134052..368b69575bb21 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,8 +1,8 @@ const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceName = "HotReloadDiagnostic" -> string! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Diagnostics.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Errors.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray diagnostics) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Refresh() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs index 439572e17b058..1cee9dbe1b173 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs @@ -16,12 +16,11 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource { + bool IDiagnosticSource.IsLiveSource() => true; Project IDiagnosticSource.GetProject() => document.Project; ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; - bool IDiagnosticSource.IsLiveSource() => true; async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs index d9b7b043cfe1d..110413bfdb9ef 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs @@ -10,18 +10,17 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = true)] IXamlDiagnosticSource? xamlDiagnosticSource) : IDiagnosticSourceProvider { bool IDiagnosticSourceProvider.IsDocument => true; - string IDiagnosticSourceProvider.Name => "XamlDiagnosticSource"; + string IDiagnosticSourceProvider.Name => "XamlDiagnostics"; ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { From 943038c0158dfde69b45e634e59261a4c1daef77 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:11:29 -0700 Subject: [PATCH 0799/1047] Fix --- .../Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs | 2 +- .../Core/Test/Venus/DocumentService_IntegrationTests.vb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 58c1dce218eb7..150d2ad7fc279 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -73,7 +73,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, - includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 0f6d62a5a75da..6338718d65607 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -238,9 +238,9 @@ class { } diagnosticService.CreateIncrementalAnalyzer(workspace) ' confirm that IDE doesn't report the diagnostics - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, - includeSuppressedDiagnostics:=False, includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.False(diagnostics.Any()) End Using End Function From 6af117c46ce34ad0b6358c3a2e59b9987c331d2b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:14:29 -0700 Subject: [PATCH 0800/1047] Simplify --- src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs | 2 +- .../Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs | 2 +- .../TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 46fd1f86dcc0e..1bf99ecb4ba35 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1080,7 +1080,7 @@ void M() : root.DescendantNodes().OfType().First().Span; await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 150d2ad7fc279..e734ac991f1ab 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -72,7 +72,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var globalOptions = exportProvider.GetExportedValue(); var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 44f697772dedd..f5767ebb02d68 100644 --- a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -56,7 +56,7 @@ private async Task> GetDiagnosticsAsync( { var text = await document.GetTextAsync().ConfigureAwait(false); var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync( filterSpan is null @@ -69,7 +69,7 @@ filterSpan is null if (getProjectDiagnostics) { var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); projectDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.DocumentId is null), project, CancellationToken.None); } From 903bdeb94590ea97ae78266f619cc3b4e61e9f41 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:15:17 -0700 Subject: [PATCH 0801/1047] Simplify --- .../Test2/Diagnostics/DiagnosticProviderTests.vb | 2 +- .../Test2/Diagnostics/DiagnosticServiceTests.vb | 12 ++++++------ .../Test/Venus/DocumentService_IntegrationTests.vb | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index df768f0d7279d..19a9f764f34be 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -262,7 +262,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim diagnosticProvider = GetDiagnosticProvider(workspace) Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result If diagnostics Is Nothing Then diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 307c1437ddec0..d6722a2eee3cf 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -537,7 +537,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(0, diagnostics.Count()) diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Dim diagnostic = diagnostics.First() Assert.True(diagnostic.Id = "AD0001") @@ -608,7 +608,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim document = project.Documents.Single() Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, diagnostics.Count()) Dim diagnostic = diagnostics.First() @@ -950,7 +950,7 @@ class AnonymousFunctions ' Verify no duplicate analysis/diagnostics. Dim document = project.Documents.Single() Dim diagnostics = (Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None)). Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id) @@ -1045,7 +1045,7 @@ class AnonymousFunctions ' Verify project diagnostics contains diagnostics reported on both partial definitions. Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, diagnostics.Count()) Dim file1HasDiag = False, file2HasDiag = False @@ -2148,7 +2148,7 @@ class MyClass ' Get diagnostics explicitly Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, hiddenDiagnostics.Count()) Assert.Equal(analyzer.Descriptor.Id, hiddenDiagnostics.Single().Id) @@ -2235,7 +2235,7 @@ class C Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(0, diagnostics.Count()) End Using diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 6338718d65607..25ca0b50529f7 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -239,7 +239,7 @@ class { } ' confirm that IDE doesn't report the diagnostics Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, Function(a) True, + workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.False(diagnostics.Any()) End Using From cacc40fa365726ca4b6ee1a7d462c31ed6018fde Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:17:27 -0700 Subject: [PATCH 0802/1047] revert --- .../Diagnostics/IDiagnosticAnalyzerService.cs | 40 +++++++++---------- .../Diagnostics/DiagnosticAnalyzerService.cs | 14 +------ ...osticIncrementalAnalyzer_GetDiagnostics.cs | 4 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 21 ++-------- ...stractWorkspaceDocumentDiagnosticSource.cs | 13 +++--- .../DocumentDiagnosticSource.cs | 6 ++- 6 files changed, 35 insertions(+), 63 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index ecc3404ea591d..a1521848a186c 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -69,11 +69,9 @@ internal interface IDiagnosticAnalyzerService Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); /// - /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned - /// should be up-to-date with respect to the given solution. Note that for project case, this method returns - /// diagnostics from all project documents as well. Use if you - /// want to fetch only project diagnostics without source locations. + /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. + /// Note that for project case, this method returns diagnostics from all project documents as well. Use + /// if you want to fetch only project diagnostics without source locations. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -82,25 +80,22 @@ internal interface IDiagnosticAnalyzerService /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. /// - /// Indicates if local document diagnostics must be returned. Local diagnostics are the ones that are reported by - /// analyzers on the same file for which the callback was received and hence can be computed by analyzing a single - /// file in isolation. + /// Indicates if local document diagnostics must be returned. + /// Local diagnostics are the ones that are reported by analyzers on the same file for which the callback was received + /// and hence can be computed by analyzing a single file in isolation. /// /// - /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by - /// analyzers either at compilation end callback OR in a different file from which the callback was made. Entire - /// project must be analyzed to get the complete set of non-local document diagnostics. + /// Indicates if non-local document diagnostics must be returned. + /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR + /// in a different file from which the callback was made. Entire project must be analyzed to get the + /// complete set of non-local document diagnostics. /// - Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, - Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + /// Cancellation token. + Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from - /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that - /// this method doesn't return any document diagnostics. Use to also fetch - /// those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. + /// Note that this method doesn't return any document diagnostics. Use to also fetch those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -108,10 +103,11 @@ Task> GetDiagnosticsForIdsAsync( /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via SuppressMessageAttributes should be returned. /// - /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by - /// analyzers either at compilation end callback. Entire project must be analyzed to get the complete set of - /// non-local diagnostics. + /// Indicates if non-local document diagnostics must be returned. + /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback. + /// Entire project must be analyzed to get the complete set of non-local diagnostics. /// + /// Cancellation token. Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index 1739673da3dbf..aac07fce0e0f2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -134,20 +134,10 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, - ProjectId? projectId, - DocumentId? documentId, - ImmutableHashSet? diagnosticIds, - Func? shouldIncludeAnalyzer, - DiagnosticKind diagnosticKind, - bool includeSuppressedDiagnostics, - bool includeLocalDocumentDiagnostics, - bool includeNonLocalDocumentDiagnostics, - CancellationToken cancellationToken) + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync( - solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 1fad5792bf29d..cb1dc8c68d0b9 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -21,8 +21,8 @@ public Task> GetCachedDiagnosticsAsync(Solution s public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index b056a182602f3..f3911586aad23 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -78,7 +78,7 @@ protected override async ValueTask> GetOrdered // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, category, cancellationToken).ConfigureAwait(false); + return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); // if it's a category we don't recognize, return nothing. return []; @@ -158,25 +158,10 @@ private static ImmutableArray GetTaskListDiagnosticSources( /// we have no workspace diagnostics to report and bail out. /// public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, string category, CancellationToken cancellationToken) + RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); - var diagnosticKind = category switch - { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - null => DiagnosticKind.All, - // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return []; - using var _ = ArrayBuilder.GetInstance(out var result); var solution = context.Solution; @@ -220,7 +205,7 @@ void AddDocumentSources(IEnumerable documents) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticKind.Value, shouldIncludeAnalyzer) + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); result.Add(documentDiagnosticSource); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 7187c68e4cfe9..4cc153fcc9739 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -12,13 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, diagnosticKind, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -35,8 +35,7 @@ public override async Task> GetDiagnosticsAsync( if (Document is SourceGeneratedDocument sourceGeneratedDocument) { // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - sourceGeneratedDocument, range: null, diagnosticKind, includeSuppressedDiagnostics: false, cancellationToken).ConfigureAwait(false); + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(sourceGeneratedDocument, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); return documentDiagnostics; } else @@ -46,8 +45,8 @@ public override async Task> GetDiagnosticsAsync( // However we can include them as a part of workspace pull when FSA is on. var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, diagnosticKind, - includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); return documentDiagnostics; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 50e3e06811c1f..54f5c6ce5deb2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { + public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; + /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -28,11 +30,11 @@ public override async Task> GetDiagnosticsAsync( // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 - if (diagnosticKind == DiagnosticKind.AnalyzerSemantic) + if (DiagnosticKind == DiagnosticKind.AnalyzerSemantic) { var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(span: null, cancellationToken).ConfigureAwait(false); allSpanDiagnostics = allSpanDiagnostics.AddRange(copilotDiagnostics); From 620ff14cad76bd6e93e18022b07ae7c2d3bdc2ca Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 10:23:40 -0700 Subject: [PATCH 0803/1047] Fix build breaks --- .../Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs | 1 - .../DiagnosticSources/AbstractProjectDiagnosticSource.cs | 4 ++-- .../Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs | 1 - .../Public/PublicWorkspacePullDiagnosticsHandler.cs | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 4a2312e0e198d..0316bd9b08909 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; - namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index b2efe993483c6..ac49b0a9da7c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -33,7 +33,7 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) { /// @@ -55,7 +55,7 @@ public override async Task> GetDiagnosticsAsync( } } - private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) + private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) : AbstractProjectDiagnosticSource(project) { /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 0a11ff5f1127f..e9a5ad49650bf 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -10,7 +10,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 545964f7b2a1a..edab103a15ec6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -36,7 +36,7 @@ public PublicWorkspacePullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) From a3212557dd2684f9021a88d7f4a3214d69f74563 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 10:46:08 -0700 Subject: [PATCH 0804/1047] More build break fixes --- .../PublicDocumentPullDiagnosticsHandler.cs | 2 +- .../WorkspacePullDiagnosticHandler.cs | 44 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 5368045e550da..426148bed2f11 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -36,7 +36,7 @@ public PublicDocumentPullDiagnosticsHandler( protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; - public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) + public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index e9258826e863f..62f6e9b5b8332 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -40,31 +40,31 @@ protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocume } ]; -protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); -protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) -{ - // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of - // these and we can save a lot of memory not serializing/deserializing all of this. - report = null; - return false; -} + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) + { + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; + } -protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); + protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); -protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) -{ - // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics - // produced by document diagnostics. - return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); -} + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + { + // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics + // produced by document diagnostics. + return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); + } -protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) -{ - return progress.GetFlattenedValues(); -} + protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); + } -internal override TestAccessor GetTestAccessor() => new(this); + internal override TestAccessor GetTestAccessor() => new(this); } From f322dbf705d4dce09f9780b188a58925a4113b47 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Tue, 23 Apr 2024 11:35:32 -0700 Subject: [PATCH 0805/1047] Add Semantic Search feature flag to pkgdef --- src/VisualStudio/Core/Def/PackageRegistration.pkgdef | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 97c945e244fa0..25767a4f03745 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -39,6 +39,12 @@ "Title"="Run C#/VB code analysis with ServerGC (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" +[$RootKey$\FeatureFlags\Roslyn\SemanticSearchEnabled] +"Description"="Enable C# Semantic Search." +"Value"=dword:00000000 +"Title"="Enable C# Semantic Search" +"PreviewPaneChannels"="IntPreview,int.main" + // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag [$RootKey$\FeatureFlags\Lsp\PullDiagnostics] "Description"="Enables the LSP-powered diagnostics for managed .Net projects" From 2c0d648f37a53a893ae4bd1b75406e7720c9d8aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 12:01:08 -0700 Subject: [PATCH 0806/1047] Remove parameter which was always passed the same value --- .../DiagnosticIncrementalAnalyzer.Executor.cs | 73 ++----------------- ...alyzer.InProcOrRemoteHostAnalyzerRunner.cs | 3 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 2 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 2 +- ...alStudioDiagnosticAnalyzerExecutorTests.cs | 12 +-- 5 files changed, 15 insertions(+), 77 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 05b432309c757..68cdf1724c25b 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -24,7 +24,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// Return all diagnostics that belong to given project for the given StateSets (analyzers) either from cache or by calculating them /// private async Task GetProjectAnalysisDataAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, bool forceAnalyzerRun, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, stateSets, cancellationToken)) { @@ -42,64 +42,7 @@ private async Task GetProjectAnalysisDataAsync( return existingData; } - // PERF: Check whether we want to analyze this project or not. - var fullAnalysisEnabled = GlobalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullAnalysisEnabled, out var analyzersFullAnalysisEnabled); - if (forceAnalyzerRun) - { - // We are forcing full solution analysis for all diagnostics. - fullAnalysisEnabled = true; - compilerFullAnalysisEnabled = true; - analyzersFullAnalysisEnabled = true; - } - - if (!fullAnalysisEnabled) - { - Logger.Log(FunctionId.Diagnostics_ProjectDiagnostic, p => $"FSA off ({p.FilePath ?? p.Name})", project); - - // If we are producing document diagnostics for some other document in this project, we still want to show - // certain project-level diagnostics that would cause file-level diagnostics to be broken. We will only do this though if - // some file that's open is depending on this project though -- that way we're going to only be analyzing projects - // that have already had compilations produced for. - var shouldProduceOutput = false; - - var projectDependencyGraph = project.Solution.GetProjectDependencyGraph(); - - foreach (var openDocumentId in project.Solution.Workspace.GetOpenDocumentIds()) - { - if (openDocumentId.ProjectId == project.Id || projectDependencyGraph.DoesProjectTransitivelyDependOnProject(openDocumentId.ProjectId, project.Id)) - { - shouldProduceOutput = true; - break; - } - } - - var results = ImmutableDictionary.Empty; - - if (shouldProduceOutput) - { - (results, _) = await UpdateWithDocumentLoadAndGeneratorFailuresAsync( - results, - project, - version, - cancellationToken).ConfigureAwait(false); - } - - return new ProjectAnalysisData(project.Id, VersionStamp.Default, existingData.Result, results); - } - - // Reduce the state sets to analyze based on individual full solution analysis values - // for compiler diagnostics and analyzers. - if (!compilerFullAnalysisEnabled) - { - Debug.Assert(analyzersFullAnalysisEnabled); - stateSets = stateSets.WhereAsArray(s => !s.Analyzer.IsCompilerAnalyzer()); - } - else if (!analyzersFullAnalysisEnabled) - { - stateSets = stateSets.WhereAsArray(s => s.Analyzer.IsCompilerAnalyzer() || s.Analyzer.IsWorkspaceDiagnosticAnalyzer()); - } - - var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun, existingData.Result, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideOptions, stateSets, existingData.Result, cancellationToken).ConfigureAwait(false); // If project is not loaded successfully, get rid of any semantic errors from compiler analyzer. // Note: In the past when project was not loaded successfully we did not run any analyzers on the project. @@ -169,7 +112,7 @@ private static async Task private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, bool forcedAnalysis, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, CancellationToken cancellationToken) { try { @@ -179,8 +122,8 @@ private async Task 0) { // calculate regular diagnostic analyzers diagnostics - var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync(project, compilationWithAnalyzers, - forcedAnalysis, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); + var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync( + project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); result = resultMap.AnalysisResult; @@ -198,7 +141,7 @@ private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, bool forcedAnalysis, + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, ImmutableDictionary existing, CancellationToken cancellationToken) { try @@ -225,12 +168,12 @@ await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync( compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, forcedAnalysis, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); return MergeExistingDiagnostics(version, existing, result); } // we couldn't reduce the set. - return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, forcedAnalysis, cancellationToken).ConfigureAwait(false); + return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index f829aa1582150..88214593ae6d4 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -50,12 +50,11 @@ public Task> AnalyzeProjectAsync( Project project, CompilationWithAnalyzers compilationWithAnalyzers, - bool forceExecuteAllAnalyzers, bool logPerformanceInfo, bool getTelemetryInfo, CancellationToken cancellationToken) => AnalyzeAsync(documentAnalysisScope: null, project, compilationWithAnalyzers, - isExplicit: false, forceExecuteAllAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken); + isExplicit: false, forceExecuteAllAnalyzers: true, logPerformanceInfo, getTelemetryInfo, cancellationToken); private async Task> AnalyzeAsync( DocumentAnalysisScope? documentAnalysisScope, diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index cb1dc8c68d0b9..5534f2fe22c94 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -269,7 +269,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, IEnumerabl // unlike the suppressed (disabled) analyzer, we will include hidden diagnostic only analyzers here. var compilation = await CreateCompilationWithAnalyzersAsync(project, ideOptions, stateSets, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, ideOptions, stateSets, forceAnalyzerRun: true, cancellationToken).ConfigureAwait(false); + var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, ideOptions, stateSets, cancellationToken).ConfigureAwait(false); foreach (var stateSet in stateSets) { diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index af943468881a4..e7054c9de2c94 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -37,7 +37,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, ideOptions, activeAnalyzers, includeSuppressedDiagnostics: true, cancellationToken).ConfigureAwait(false); - var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun: true, cancellationToken).ConfigureAwait(false); + var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(out var diagnostics); diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs index 51c11425410a8..4eaec828a85b0 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs @@ -192,11 +192,7 @@ void Method() analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(), new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideAnalyzerOptions)); - // no result for open file only analyzer unless forced - var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: false, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); - Assert.Empty(result.AnalysisResult); - - result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: true, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); + var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.Analyzers[0]]; // check result @@ -234,7 +230,7 @@ void Method() var compilationWithAnalyzers = (await project.GetCompilationAsync()) .WithAnalyzers(analyzers, new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideAnalyzerOptions)); - var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: false, + var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.Analyzers[0]]; @@ -258,8 +254,8 @@ private static async Task AnalyzeAsync(TestWorkspace w analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(), new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideOptions)); - var result = await executor.AnalyzeProjectAsync(project, analyzerDriver, forceExecuteAllAnalyzers: true, logPerformanceInfo: false, - getTelemetryInfo: false, cancellationToken); + var result = await executor.AnalyzeProjectAsync( + project, analyzerDriver, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken); return result.AnalysisResult[analyzerDriver.Analyzers[0]]; } From 28a348df21e1ff173fa206aa86fb4042486e2614 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 12:13:06 -0700 Subject: [PATCH 0807/1047] Fix test regressions --- .../Handler/Diagnostics/AbstractPullDiagnosticHandler.cs | 2 -- .../Diagnostics/AbstractPullDiagnosticTestsBase.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 85177e1a7fec9..94bb3f537e1c9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -37,8 +37,6 @@ internal abstract partial class AbstractPullDiagnosticHandler protected const int WorkspaceDiagnosticIdentifier = 1; protected const int DocumentDiagnosticIdentifier = 2; - // internal for testing purposes - internal const int DocumentNonLocalDiagnosticIdentifier = 3; private readonly IDiagnosticsRefresher _diagnosticRefresher; protected readonly IGlobalOptionService GlobalOptions; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 201f98f4233cf..73231c4dc1bf1 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -303,7 +303,7 @@ static DocumentDiagnosticParams CreateProposedDocumentDiagnosticParams( { return new DocumentDiagnosticParams { - Identifier = testNonLocalDiagnostics ? DocumentPullDiagnosticHandler.DocumentNonLocalDiagnosticIdentifier.ToString() : null, + Identifier = testNonLocalDiagnostics ? PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal : null, PreviousResultId = previousResultId, PartialResultToken = progress, TextDocument = vsTextDocumentIdentifier, From 01f0b909f5d41cf60b20e69cd6fc12833a7ac14d Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 23 Apr 2024 13:35:22 -0700 Subject: [PATCH 0808/1047] Address unstable ordering in AbstractBuiltInCodeStyleDiagnosticAnalyzer (#73189) * Address unstable ordering in AbstractBuiltInCodeStyleDiagnosticAnalyzer This class was previously non-deterministic about the value that it would assign to Descriptor based on it's ctor input. The process it previously used was to take the ImmutableDictionary it took into it's ctor and convert the keys from that dictionary into an immutable aray. From that, it would use the first item as the descrptor. This would lead to unstable values being used, as evidenced in https://github.com/dotnet/roslyn/issues/73070. Additionally, as DiagnosticDescriptor's IEquatable didn't use CustomTags to differentiate between descriptors, I saw that several items being added to the dictionary by some callers were being duplicated to each other. As part of this PR, I changed the AbstractBuiltInCodeStyleDiagnosticAnalyzer to no longer take in an ImmutableDictionary, but rather an ImmutableArray with tuples of the keys/values. This allows the caller to control a stable ordering of descriptors. --- ...placedUsingDirectivesDiagnosticAnalyzer.cs | 11 ++-- ...eCollectionExpressionDiagnosticAnalyzer.cs | 14 ++--- .../UseExpressionBodyDiagnosticAnalyzer.cs | 11 ++-- ...pressionBodyForLambdaDiagnosticAnalyzer.cs | 8 +-- ...BuiltInCodeStyleDiagnosticAnalyzerTests.cs | 59 +++++++++++++++++++ .../Tests/CSharpAnalyzers.UnitTests.projitems | 4 ++ ...tractBuiltInCodeStyleDiagnosticAnalyzer.cs | 9 ++- ...BuiltInCodeStyleDiagnosticAnalyzer_Core.cs | 3 - ...nUnnecessaryCodeStyleDiagnosticAnalyzer.cs | 8 +-- .../AbstractFileHeaderDiagnosticAnalyzer.cs | 10 ++-- ...edParametersAndValuesDiagnosticAnalyzer.cs | 12 ++-- ...SimplifyTypeNamesDiagnosticAnalyzerBase.cs | 25 ++++---- ...CollectionInitializerDiagnosticAnalyzer.cs | 8 +-- ...tUseObjectInitializerDiagnosticAnalyzer.cs | 8 +-- 14 files changed, 122 insertions(+), 68 deletions(-) create mode 100644 src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs diff --git a/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs index 0295d3a9908cc..7072dff8597cf 100644 --- a/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs @@ -3,15 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives; @@ -40,9 +37,11 @@ internal sealed class MisplacedUsingDirectivesDiagnosticAnalyzer : AbstractBuilt s_localizableTitle, s_localizableInsideMessage); public MisplacedUsingDirectivesDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_outsideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement) - .Add(s_insideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement)) + : base( + [ + (s_outsideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement), + (s_insideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement) + ]) { } diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs index 7fe59d662f13d..b1daeecb8190a 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseCollectionInitializer; @@ -22,18 +21,15 @@ internal abstract class AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer public static readonly ImmutableDictionary ChangesSemantics = ImmutableDictionary.Empty.Add(UseCollectionInitializerHelpers.ChangesSemanticsName, ""); - protected new readonly DiagnosticDescriptor Descriptor; protected readonly DiagnosticDescriptor UnnecessaryCodeDescriptor; protected AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer(string diagnosticId, EnforceOnBuild enforceOnBuild) - : base(ImmutableDictionary.Empty - // Ugly hack. We need to create a descriptor to pass to our base *and* assign to one of our fields. - // The conditional pattern form lets us do that. - .Add(CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: false) is var descriptor ? descriptor : null, CodeStyleOptions2.PreferCollectionExpression) - .Add(CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: true) is var unnecessaryCodeDescriptor ? unnecessaryCodeDescriptor : null, CodeStyleOptions2.PreferCollectionExpression)) + : base( + [ + (CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: false), CodeStyleOptions2.PreferCollectionExpression) + ]) { - Descriptor = descriptor; - UnnecessaryCodeDescriptor = unnecessaryCodeDescriptor; + UnnecessaryCodeDescriptor = CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: true); } private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, EnforceOnBuild enforceOnBuild, bool isUnnecessary) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs index bed23c5b6d1a6..fa00a7e87a56b 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; @@ -14,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody; [DiagnosticAnalyzer(LanguageNames.CSharp)] -internal class UseExpressionBodyDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer +internal sealed class UseExpressionBodyDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer { public const string FixesError = nameof(FixesError); @@ -28,16 +27,16 @@ public UseExpressionBodyDiagnosticAnalyzer() _syntaxKinds = _helpers.SelectManyAsArray(h => h.SyntaxKinds); } - private static ImmutableDictionary GetSupportedDescriptorsWithOptions() + private static ImmutableArray<(DiagnosticDescriptor, IOption2)> GetSupportedDescriptorsWithOptions() { - var builder = ImmutableDictionary.CreateBuilder(); + var builder = new FixedSizeArrayBuilder<(DiagnosticDescriptor, IOption2)>(_helpers.Length); foreach (var helper in _helpers) { var descriptor = CreateDescriptorWithId(helper.DiagnosticId, helper.EnforceOnBuild, hasAnyCodeStyleOption: true, helper.UseExpressionBodyTitle, helper.UseExpressionBodyTitle); - builder.Add(descriptor, helper.Option); + builder.Add((descriptor, helper.Option)); } - return builder.ToImmutable(); + return builder.MoveToImmutable(); } public override DiagnosticAnalyzerCategory GetAnalyzerCategory() diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index d4248991e4b42..21af7ba3f40bd 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; @@ -21,9 +20,10 @@ internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBui private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle, UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle); public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base( - ImmutableDictionary.Empty - .Add(s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) - .Add(s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas)) + [ + (s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas), + (s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) + ]) { } diff --git a/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs b/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs new file mode 100644 index 0000000000000..616ccf33ffd80 --- /dev/null +++ b/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AbstractBuiltInCodeStyleDiagnosticAnalyzer; + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; +using Xunit; + +public sealed class AbstractBuiltInCodeStyleDiagnosticAnalyzerTests +{ + [Fact] + public void VerifyDiagnosticDescriptorOrderingMaintained() + { + var ids = Enumerable.Range(10, 20).Select(item => "IDE_" + item); + + var analyzer = new TestAnalyzer(ids); + + Assert.Equal(analyzer.SupportedDiagnostics.Select(static diagnostic => diagnostic.Id), ids); + } + + private sealed class TestAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public TestAnalyzer(IEnumerable ids) + : base(CreateSupportedDiagnosticsWithOptionsFromIds(ids)) + { + } + + private static ImmutableArray<(DiagnosticDescriptor, ImmutableHashSet)> CreateSupportedDiagnosticsWithOptionsFromIds(IEnumerable ids) + { + var builder = ImmutableArray.CreateBuilder<(DiagnosticDescriptor, ImmutableHashSet)>(); + foreach (var id in ids) + { + var descriptor = CreateDescriptorWithId( + id: id, + enforceOnBuild: EnforceOnBuild.Never, + hasAnyCodeStyleOption: false, + title: string.Empty); + + builder.Add((descriptor, [])); + } + + return builder.ToImmutableAndClear(); + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => throw new System.NotImplementedException(); + + protected override void InitializeWorker(AnalysisContext context) + { + } + } +} diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index 1ce11ff7471ba..472b6d7133ff3 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -9,6 +9,7 @@ Microsoft.CodeAnalysis.CSharp.Analyzers.UnitTests + @@ -181,4 +182,7 @@ + + + \ No newline at end of file diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs index 2a9338532b838..9b8b81878119e 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; @@ -81,8 +80,8 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer( /// /// Constructor for a code style analyzer with a multiple diagnostic descriptors with a code style editorconfig option that can be used to configure each descriptor. /// - protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary supportedDiagnosticsWithOptions) - : this(supportedDiagnosticsWithOptions.Keys.ToImmutableArray()) + protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, IOption2 Option)> supportedDiagnosticsWithOptions) + : this(supportedDiagnosticsWithOptions.SelectAsArray(static item => item.Descriptor)) { foreach (var (descriptor, option) in supportedDiagnosticsWithOptions) { @@ -94,8 +93,8 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary /// Constructor for a code style analyzer with multiple diagnostic descriptors with zero or more code style editorconfig options that can be used to configure each descriptor. /// - protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary> supportedDiagnosticsWithOptions) - : this(supportedDiagnosticsWithOptions.Keys.ToImmutableArray()) + protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, ImmutableHashSet Options)> supportedDiagnosticsWithOptions) + : this(supportedDiagnosticsWithOptions.SelectAsArray(static item => item.Descriptor)) { foreach (var (descriptor, options) in supportedDiagnosticsWithOptions) { diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs index 59d80030b5808..b91fd230bf02f 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeStyle; diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs index 4ca8ddd49631f..922dc5ad9ff11 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs @@ -100,19 +100,19 @@ protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray /// Constructor for a code style analyzer with a multiple diagnostic descriptors with a code style options that can be used to configure each descriptor. /// - protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableDictionary supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) + protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, IOption2 Option)> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) : base(supportedDiagnosticsWithOptions) { - AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Keys, fadingOption); + AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Select(static item => item.Descriptor), fadingOption); } /// /// Constructor for a code style analyzer with multiple diagnostic descriptors with zero or more code style options that can be used to configure each descriptor. /// - protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableDictionary> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) + protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, ImmutableHashSet Options)> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) : base(supportedDiagnosticsWithOptions) { - AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Keys, fadingOption); + AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Select(static item => item.Descriptor), fadingOption); } private static void AddDiagnosticIdToFadingOptionMapping(string diagnosticId, PerLanguageOption2? fadingOption) diff --git a/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs index e356bc1c9b206..9f8bfae89f846 100644 --- a/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using System.IO; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.FileHeaders; @@ -24,9 +22,11 @@ private static DiagnosticDescriptor CreateDescriptorForFileHeader(LocalizableStr => CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: true, title, message); protected AbstractFileHeaderDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate) - .Add(s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate)) + : base( + [ + (s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate), + (s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate) + ]) { } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs index 0240858e09729..42846f4bc11e2 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs @@ -96,11 +96,13 @@ internal abstract partial class AbstractRemoveUnusedParametersAndValuesDiagnosti protected AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer( Option2> unusedValueExpressionStatementOption, Option2> unusedValueAssignmentOption) - : base(ImmutableDictionary.Empty - .Add(s_expressionValueIsUnusedRule, unusedValueExpressionStatementOption) - .Add(s_valueAssignedIsUnusedRule, unusedValueAssignmentOption) - .Add(s_unusedParameterRule, CodeStyleOptions2.UnusedParameters), - fadingOption: null) + : base( + [ + (s_expressionValueIsUnusedRule, unusedValueExpressionStatementOption), + (s_valueAssignedIsUnusedRule, unusedValueAssignmentOption), + (s_unusedParameterRule, CodeStyleOptions2.UnusedParameters) + ], + fadingOption: null) { } diff --git a/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs b/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs index a37aa66a8d07f..fdaf88e7069f3 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs @@ -4,19 +4,16 @@ // #define LOG -using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -64,15 +61,17 @@ internal abstract class SimplifyTypeNamesDiagnosticAnalyzerBase>.Empty - .Add(s_descriptorSimplifyNames, []) - .Add(s_descriptorSimplifyMemberAccess, []) - .Add(s_descriptorPreferBuiltinOrFrameworkType, - [ - CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, - CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, - ]), - fadingOption: null) + : base( + [ + (s_descriptorSimplifyNames, []), + (s_descriptorSimplifyMemberAccess, []), + (s_descriptorPreferBuiltinOrFrameworkType, + [ + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, + ]) + ], + fadingOption: null) { } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs index ea645fb31ee2a..a663612d196d2 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.CodeStyle; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -82,9 +81,10 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() isUnnecessary: true); protected AbstractUseCollectionInitializerDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_descriptor, CodeStyleOptions2.PreferCollectionInitializer) - .Add(s_unnecessaryCodeDescriptor, CodeStyleOptions2.PreferCollectionInitializer)) + : base( + [ + (s_descriptor, CodeStyleOptions2.PreferCollectionInitializer) + ]) { } diff --git a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs index 8e1c39d240270..aabbb18b1eb49 100644 --- a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; @@ -62,9 +61,10 @@ internal abstract partial class AbstractUseObjectInitializerDiagnosticAnalyzer< protected abstract TAnalyzer GetAnalyzer(); protected AbstractUseObjectInitializerDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_descriptor, CodeStyleOptions2.PreferObjectInitializer) - .Add(s_unnecessaryCodeDescriptor, CodeStyleOptions2.PreferObjectInitializer)) + : base( + [ + (s_descriptor, CodeStyleOptions2.PreferObjectInitializer) + ]) { } From d3f3ff3ac5bb7a5a8f08240896da7dcfe7f4af4f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:00:31 -0700 Subject: [PATCH 0809/1047] Add more information in checksums --- .../Workspace/Solution/StateChecksums.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index dd1c5d79ec793..3ad15235208ad 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,6 +486,23 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } +<<<<<<< Updated upstream +======= + + public override string ToString() + => $""" + ProjectStateChecksums({ProjectId}) + Info={Info} + CompilationOptions={CompilationOptions} + ParseOptions={ParseOptions} + ProjectReferences={ProjectReferences.Checksum} + MetadataReferences={MetadataReferences.Checksum} + AnalyzerReferences={AnalyzerReferences.Checksum} + Documents={Documents.Checksum} + AdditionalDocuments={AdditionalDocuments.Checksum} + AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} + """; +>>>>>>> Stashed changes } internal sealed class DocumentStateChecksums( From bf0d0196e3d1e3127c778e7308224833ef83820b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:01:35 -0700 Subject: [PATCH 0810/1047] Apply suggestions from code review --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 3ad15235208ad..fd3cf01c230bc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,8 +486,6 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } -<<<<<<< Updated upstream -======= public override string ToString() => $""" @@ -502,7 +500,6 @@ public override string ToString() AdditionalDocuments={AdditionalDocuments.Checksum} AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} """; ->>>>>>> Stashed changes } internal sealed class DocumentStateChecksums( From 53e49f66d71b1f63195c7571b3f37ec6a17fbace Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:03:22 -0700 Subject: [PATCH 0811/1047] Fix --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 3ad15235208ad..fd3cf01c230bc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,8 +486,6 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } -<<<<<<< Updated upstream -======= public override string ToString() => $""" @@ -502,7 +500,6 @@ public override string ToString() AdditionalDocuments={AdditionalDocuments.Checksum} AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} """; ->>>>>>> Stashed changes } internal sealed class DocumentStateChecksums( From 0c3133272c2e2c597114a6abec8037b7fd75db82 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:20:59 -0700 Subject: [PATCH 0812/1047] Cache diagnostics produced during workspace pull, to avoid unnecessary computations Docs --- ...stractWorkspaceDocumentDiagnosticSource.cs | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..53b3937749c0d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -3,10 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -21,6 +25,13 @@ public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDia private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { + /// + /// Cached mapping between a project instance and all the diagnostics computed for it. This is used so that + /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we + /// walk every document within it. + /// + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -40,14 +51,32 @@ public override async Task> GetDiagnosticsAsync( } else { - // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document - // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. - // However we can include them as a part of workspace pull when FSA is on. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); - return documentDiagnostics; + var projectDiagnostics = await GetProjectDiagnosticsAsync(diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + return projectDiagnostics.WhereAsArray(d => d.DocumentId == Document.Id); + } + } + + private async ValueTask> GetProjectDiagnosticsAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + if (!s_projectToDiagnostics.TryGetValue(Document.Project, out var lazyDiagnostics)) + { + // Extracted into local to prevent captures. + lazyDiagnostics = GetLazyDiagnostics(); + } + + var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); + return (ImmutableArray)result; + + AsyncLazy> GetLazyDiagnostics() + { + return s_projectToDiagnostics.GetValue( + Document.Project, + _ => AsyncLazy.Create>( + async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); } } } From c4c969fa37238bdaca13eaccdf6a3406d6c409bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:20:59 -0700 Subject: [PATCH 0813/1047] Cache diagnostics produced during workspace pull, to avoid unnecessary computations Docs --- ...stractWorkspaceDocumentDiagnosticSource.cs | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..53b3937749c0d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -3,10 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -21,6 +25,13 @@ public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDia private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { + /// + /// Cached mapping between a project instance and all the diagnostics computed for it. This is used so that + /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we + /// walk every document within it. + /// + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -40,14 +51,32 @@ public override async Task> GetDiagnosticsAsync( } else { - // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document - // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. - // However we can include them as a part of workspace pull when FSA is on. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); - return documentDiagnostics; + var projectDiagnostics = await GetProjectDiagnosticsAsync(diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + return projectDiagnostics.WhereAsArray(d => d.DocumentId == Document.Id); + } + } + + private async ValueTask> GetProjectDiagnosticsAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + if (!s_projectToDiagnostics.TryGetValue(Document.Project, out var lazyDiagnostics)) + { + // Extracted into local to prevent captures. + lazyDiagnostics = GetLazyDiagnostics(); + } + + var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); + return (ImmutableArray)result; + + AsyncLazy> GetLazyDiagnostics() + { + return s_projectToDiagnostics.GetValue( + Document.Project, + _ => AsyncLazy.Create>( + async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); } } } From a9e72d6d1a1efb941a25c1c8375271c6f3efe46a Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 15:11:03 -0700 Subject: [PATCH 0814/1047] CR feedback --- .../DiagnosticSourceManager.cs | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 0482e5c8b9144..d255fd0fc6f97 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -48,8 +48,12 @@ public async ValueTask> CreateDiagnosticSource var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; if (sourceName != null) { - Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); - return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + // VS does not distinguish between document and workspace sources. Thus it can request + // document diagnostics with workspace source name. We need to handle this case. + if (providersDictionary.TryGetValue(sourceName, out var provider)) + return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + + return []; } else { @@ -57,7 +61,7 @@ public async ValueTask> CreateDiagnosticSource using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); foreach (var kvp in providersDictionary) { - // Exclude task diagnostics from the aggregated sources. + // Exclude Task diagnostics from the aggregated sources. if (kvp.Key != PullDiagnosticCategories.Task) { var namedSources = await kvp.Value.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); @@ -66,32 +70,57 @@ public async ValueTask> CreateDiagnosticSource } var sources = sourcesBuilder.ToImmutableAndClear(); - if (!isDocument || sources.Length <= 1) + if (sources.Length <= 1) { return sources; } - else + + if (isDocument) { - // VS Code document handler (and legacy VS ?) expects a single source for document diagnostics. - // For more details see PublicDocumentPullDiagnosticsHandler.CreateReturn. + // Group all document sources into a single source. Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); - return [new AggregatedDocumentDiagnosticSource(sources)]; + sources = [new AggregatedDocumentDiagnosticSource(sources)]; + } + else + { + // For workspace we need to group sources by document and IsLiveSource + sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) + .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) + .ToImmutableArray(); } + + return sources; } } - private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + private class AggregatedDocumentDiagnosticSource : IDiagnosticSource { + private readonly ImmutableArray _sources; + + public static ImmutableArray AggregateIfNeeded(IEnumerable sources) + { + var result = sources.ToImmutableArray(); + if (result.Length > 1) + { + result = [new AggregatedDocumentDiagnosticSource(result)]; + } + + return result; + } + + public AggregatedDocumentDiagnosticSource(ImmutableArray sources) + => this._sources = sources; + public bool IsLiveSource() => true; - public Project GetProject() => sources[0].GetProject(); - public ProjectOrDocumentId GetId() => sources[0].GetId(); - public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); - public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + public Project GetProject() => _sources[0].GetProject(); + public ProjectOrDocumentId GetId() => _sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => _sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={_sources.Length}"; public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var source in sources) + foreach (var source in _sources) { var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); diagnostics.AddRange(namedDiagnostics); From 6dfc2597165104385b986a3cf5c3eb7a80522580 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 17:17:16 -0700 Subject: [PATCH 0815/1047] In progrss --- ...tionCompilationState.CompilationTracker.cs | 46 ++++++------------- ...eneratedFileReplacingCompilationTracker.cs | 12 ----- ...ionCompilationState.ICompilationTracker.cs | 1 - .../Solution/SolutionCompilationState.cs | 27 +++-------- .../CoreTest/SolutionTests/SolutionTests.cs | 3 +- 5 files changed, 21 insertions(+), 68 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index d38606df450db..11b1bd8884d08 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -529,18 +529,23 @@ await compilationState.GetCompilationAsync( if (creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.Create) { - // Client always wants an up to date metadata reference. Produce one for this project reference. - var metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + // Client always wants an up to date metadata reference. Produce one for this project + // reference. Because the policy is to always 'Create' here, we include cross language + // references, producing skeletons for them if necessary. + var metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: true, cancellationToken).ConfigureAwait(false); AddMetadataReference(projectReference, metadataReference); } else { Contract.ThrowIfFalse(creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent or SkeletonReferenceCreationPolicy.DoNotCreate); - // If not asked to explicit create an up to date skeleton, attempt to get a partial - // reference, or fallback to the last successful reference for this project if we can - // find one. - var metadataReference = compilationState.GetPartialMetadataReference(projectReference, this.ProjectState); + // Client does not want to force a skeleton reference to be created. Try to get a + // metadata reference cheaply in the case where this is a reference to the same + // language. If that fails, also attempt to get a reference to a skeleton assembly + // produced from one of our prior stale compilations. + var metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: false, cancellationToken).ConfigureAwait(false); if (metadataReference is null) { var inProgressCompilationNotRef = staleCompilationWithGeneratedDocuments ?? compilationWithoutGeneratedDocuments; @@ -552,7 +557,8 @@ await compilationState.GetCompilationAsync( // create a real skeleton here. if (metadataReference is null && creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent) { - metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: true, cancellationToken).ConfigureAwait(false); } AddMetadataReference(projectReference, metadataReference); @@ -647,32 +653,6 @@ private Compilation CreateEmptyCompilation() } } - /// - /// Attempts to get (without waiting) a metadata reference to a possibly in progress - /// compilation. Only actual compilation references are returned. Could potentially - /// return null if nothing can be provided. - /// - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) - { - if (ProjectState.LanguageServices == fromProject.LanguageServices) - { - // if we have a compilation and its the correct language, use a simple compilation reference in any - // state it happens to be in right now - if (ReadState() is CompilationTrackerState compilationState) - return compilationState.CompilationWithoutGeneratedDocuments.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); - } - else - { - // Cross project reference. We need a skeleton reference. Skeletons are too expensive to - // generate on demand. So just try to see if we can grab the last generated skeleton for that - // project. - var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); - return _skeletonReferenceCache.TryGetAlreadyBuiltMetadataReference(properties); - } - - return null; - } - public Task HasSuccessfullyLoadedAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 990d4467db2a7..83dd3fa0a8ce6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -147,18 +147,6 @@ private async Task ComputeDependentChecksumAsync(SolutionCompilationSt await UnderlyingTracker.GetDependentChecksumAsync(compilationState, cancellationToken).ConfigureAwait(false), (await _replacementDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) - { - // This method is used if you're forking a solution with partial semantics, and used to quickly produce references. - // So this method should only be called if: - // - // 1. Project A has a open source generated document, and this CompilationTracker represents A - // 2. Project B references that A, and is being frozen for partial semantics. - // - // We generally don't use partial semantics in a different project than the open file, so this isn't a scenario we need to support. - throw new NotImplementedException(); - } - public async ValueTask> GetSourceGeneratedDocumentStatesAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index c910b9301214d..30455ec7cfd49 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -49,7 +49,6 @@ private interface ICompilationTracker Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); Task GetDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference); ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask GetSourceGeneratorRunResultAsync(SolutionCompilationState solution, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 960c7f165945d..058055eb643fe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -1009,24 +1009,6 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( return GetCompilationTracker(documentId.ProjectId).TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); } - /// - /// Attempt to get the best readily available compilation for the project. It may be a - /// partially built compilation. - /// - private MetadataReference? GetPartialMetadataReference( - ProjectReference projectReference, - ProjectState fromProject) - { - // Try to get the compilation state for this project. If it doesn't exist, don't do any - // more work. - if (!_projectIdToTrackerMap.TryGetValue(projectReference.ProjectId, out var state)) - { - return null; - } - - return state.GetPartialMetadataReference(fromProject, projectReference); - } - /// /// Get a metadata reference to this compilation info's compilation with respect to /// another project. For cross language references produce a skeletal assembly. If the @@ -1034,7 +1016,7 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( /// needed and does not exist, it is also built. /// private async Task GetMetadataReferenceAsync( - ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) + ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, bool includeCrossLanguage, CancellationToken cancellationToken) { try { @@ -1046,6 +1028,9 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); } + if (!includeCrossLanguage) + return null; + // otherwise get a metadata only image reference that is built by emitting the metadata from the // referenced project's compilation and re-importing it. using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) @@ -1065,14 +1050,14 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( /// can happen when trying to build a skeleton reference that fails to build. /// public Task GetMetadataReferenceAsync( - ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) + ProjectReference projectReference, ProjectState fromProject, bool includeCrossLanguage, CancellationToken cancellationToken) { try { // Get the compilation state for this project. If it's not already created, then this // will create it. Then force that state to completion and get a metadata reference to it. var tracker = this.GetCompilationTracker(projectReference.ProjectId); - return GetMetadataReferenceAsync(tracker, fromProject, projectReference, cancellationToken); + return GetMetadataReferenceAsync(tracker, fromProject, projectReference, includeCrossLanguage, cancellationToken); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) { diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index e1ac87c0b8243..67e9cabedec10 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2216,7 +2216,8 @@ private static async Task ValidateSolutionAndCompilationsAsync(Solution solution { if (solution.ContainsProject(referenced.ProjectId)) { - var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync(referenced, solution.GetProjectState(project.Id), CancellationToken.None); + var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync( + referenced, solution.GetProjectState(project.Id), includeCrossLanguage: true, CancellationToken.None); Assert.NotNull(referencedMetadata); if (referencedMetadata is CompilationReference compilationReference) { From 3b3fbd7f1b64ea88a5be4953c648e640fc202385 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 16:37:50 -0700 Subject: [PATCH 0816/1047] Update to support additional documents --- .../Test/CodeFixes/CodeFixServiceTests.cs | 2 +- .../DiagnosticAnalyzerServiceTests.cs | 6 +----- .../MockDiagnosticAnalyzerService.cs | 2 +- .../Diagnostics/IDiagnosticAnalyzerService.cs | 17 ++++++++++++++--- .../Diagnostics/DiagnosticAnalyzerService.cs | 4 ++-- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 19 ++++++++++++------- ...stractWorkspaceDocumentDiagnosticSource.cs | 6 +++++- .../ExternalDiagnosticUpdateSourceTests.vb | 2 +- 8 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 1bf99ecb4ba35..0594aa086c502 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1080,7 +1080,7 @@ void M() : root.DescendantNodes().OfType().First().Span; await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index e734ac991f1ab..2c7e97f9e7084 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -12,20 +12,16 @@ using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.CSharp; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Editor.Test; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote.Diagnostics; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests; using Roslyn.Test.Utilities; using Roslyn.Test.Utilities.TestGenerators; using Roslyn.Utilities; @@ -72,7 +68,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var globalOptions = exportProvider.GetExportedValue(); var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs index 6bd490efea2e5..424a6876218c1 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs @@ -61,7 +61,7 @@ public Task> GetCachedDiagnosticsAsync(Workspace public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task> GetDiagnosticsForSpanAsync(TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, Func? addOperationScope, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index f2099d71bf024..6ac5f90bcae97 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -75,11 +76,13 @@ internal interface IDiagnosticAnalyzerService /// complete set of non-local document diagnostics. /// /// Cancellation token. - Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocumentIds, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that this method doesn't return any document diagnostics. Use to also fetch those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from + /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that + /// this method doesn't return any document diagnostics. Use to also fetch + /// those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -181,4 +184,12 @@ public static Task> GetDiagnosticsForSpanAsync(th includeCompilerDiagnostics: true, includeSuppressedDiagnostics, priorityProvider, addOperationScope, diagnosticKind, isExplicit, cancellationToken); } + + public static Task> GetDiagnosticsForIdsAsync( + this IDiagnosticAnalyzerService service, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + { + return service.GetDiagnosticsForIdsAsync( + solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocumentIds: null, + includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index d2d7ff6d55715..be32623daf749 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -128,10 +128,10 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 4341ec3017262..f24820a50f801 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -18,11 +18,11 @@ internal partial class DiagnosticIncrementalAnalyzer public Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeCachedDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); private abstract class DiagnosticGetter { @@ -35,6 +35,8 @@ private abstract class DiagnosticGetter protected readonly bool IncludeLocalDocumentDiagnostics; protected readonly bool IncludeNonLocalDocumentDiagnostics; + private readonly Func> _getDocuments; + private ImmutableArray.Builder? _lazyDataBuilder; public DiagnosticGetter( @@ -42,6 +44,7 @@ public DiagnosticGetter( Solution solution, ProjectId? projectId, DocumentId? documentId, + Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) @@ -50,6 +53,7 @@ public DiagnosticGetter( Solution = solution; DocumentId = documentId; + _getDocuments = getDocuments ?? (static (project, documentId) => documentId != null ? [documentId] : project.DocumentIds); ProjectId = projectId ?? documentId?.ProjectId; IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; @@ -76,7 +80,7 @@ public async Task> GetDiagnosticsAsync(Cancellati return GetDiagnosticData(); } - var documentIds = DocumentId != null ? [DocumentId] : project.DocumentIds; + var documentIds = _getDocuments(project, DocumentId); // return diagnostics specific to one project or document var includeProjectNonLocalResult = DocumentId == null; @@ -129,7 +133,7 @@ private bool ShouldIncludeSuppressedDiagnostic(DiagnosticData diagnostic) private sealed class IdeCachedDiagnosticGetter : DiagnosticGetter { public IdeCachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { } @@ -229,8 +233,9 @@ private sealed class IdeLatestDiagnosticGetter : DiagnosticGetter public IdeLatestDiagnosticGetter( DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + Func>? getDocuments, + bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { _diagnosticIds = diagnosticIds; _shouldIncludeAnalyzer = shouldIncludeAnalyzer; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 53b3937749c0d..cc301aa60d8b3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -75,7 +76,10 @@ AsyncLazy> GetLazyDiagnostics() _ => AsyncLazy.Create>( async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); } } diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 7b9b26917d379..edbf03f93fae6 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -430,7 +430,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync + Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), getDocuments As Func(Of Project, DocumentId, IReadOnlyList(Of DocumentId)), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function From 23d84c480e81b2f9e14080f0a2b51f6dee72c6c7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 16:37:50 -0700 Subject: [PATCH 0817/1047] Update to support additional documents Update to support additional documents --- .../Test/CodeFixes/CodeFixServiceTests.cs | 5 +++-- .../DiagnosticAnalyzerServiceTests.cs | 2 -- .../MockDiagnosticAnalyzerService.cs | 2 +- .../Diagnostics/IDiagnosticAnalyzerService.cs | 17 ++++++++++++--- .../Diagnostics/DiagnosticAnalyzerService.cs | 4 ++-- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 21 ++++++++++++------- ...stractWorkspaceDocumentDiagnosticSource.cs | 6 +++++- .../ExternalDiagnosticUpdateSourceTests.vb | 2 +- 8 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 815c9aee54ca7..fe58043db8f44 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1084,8 +1084,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/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 2f120d8c2d5da..04fe05515d323 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -12,9 +12,7 @@ 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; diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs index 6bd490efea2e5..424a6876218c1 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs @@ -61,7 +61,7 @@ public Task> GetCachedDiagnosticsAsync(Workspace public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task> GetDiagnosticsForSpanAsync(TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, Func? addOperationScope, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 6b7d1a2feef80..34fb28f4ec7f6 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -9,6 +9,7 @@ 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; @@ -94,11 +95,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. @@ -200,4 +203,12 @@ public static Task> GetDiagnosticsForSpanAsync(th includeCompilerDiagnostics: true, includeSuppressedDiagnostics, priorityProvider, addOperationScope, diagnosticKind, isExplicit, cancellationToken); } + + public static Task> GetDiagnosticsForIdsAsync( + this IDiagnosticAnalyzerService service, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + { + return service.GetDiagnosticsForIdsAsync( + solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocumentIds: null, + includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index c95c3227c185d..8550f899852e0 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -142,10 +142,10 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 7bf0a3fbb7bf0..3e7cef8958ab3 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -19,13 +19,13 @@ public Task> GetCachedDiagnosticsAsync(Solution s => 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); + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: 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 +38,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 +47,7 @@ public DiagnosticGetter( Solution solution, ProjectId? projectId, DocumentId? documentId, + Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) @@ -53,6 +56,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 +83,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 +136,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 +236,9 @@ private sealed class IdeLatestDiagnosticGetter : DiagnosticGetter public IdeLatestDiagnosticGetter( DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + Func>? getDocuments, + bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { _diagnosticIds = diagnosticIds; _shouldIncludeAnalyzer = shouldIncludeAnalyzer; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 53b3937749c0d..cc301aa60d8b3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -75,7 +76,10 @@ AsyncLazy> GetLazyDiagnostics() _ => AsyncLazy.Create>( async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); } } diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 08517a6f552e3..60681166d0967 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -662,7 +662,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync + Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), getDocuments As Func(Of Project, DocumentId, IReadOnlyList(Of DocumentId)), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function From e535a26a8a32b593b73d24ae46bb2e05e864041b Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 17:42:45 -0700 Subject: [PATCH 0818/1047] CR feedback --- .../Options/SolutionCrawlerOptionsStorage.cs | 1 - ...bstractDocumentDiagnosticSourceProvider.cs | 24 ----- .../AbstractDocumentPullDiagnosticHandler.cs | 29 +++++- ...stractWorkspaceDiagnosticSourceProvider.cs | 8 -- ...AbstractWorkspacePullDiagnosticsHandler.cs | 8 ++ .../DiagnosticSourceManager.cs | 20 ++-- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 4 +- .../DocumentTaskDiagnosticSourceProvider.cs | 5 +- ...ocumentNonLocalDiagnosticSourceProvider.cs | 11 +-- ...cePullDiagnosticsHandler_IOnInitialized.cs | 18 +--- ...mentsAndProjectDiagnosticSourceProvider.cs | 95 +++++++++---------- ...EditAndContinueDiagnosticSourceProvider.cs | 9 +- .../WorkspaceTaskDiagnosticSourceProvider.cs | 25 +++-- 13 files changed, 114 insertions(+), 143 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs index 8aacbe48a42fe..cc7fc24bc3875 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs index f5f014ddbde07..74984ee495ce4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs @@ -14,28 +14,4 @@ internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : public string Name => name; public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); - - protected static TextDocument? GetOpenDocument(RequestContext context) - { - // Note: context.Document may be null in the case where the client is asking about a document that we have - // since removed from the workspace. In this case, we don't really have anything to process. - // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. - // - // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each - // handler treats those as separate worlds that they are responsible for. - var textDocument = context.TextDocument; - if (textDocument is null) - { - context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return null; - } - - if (!context.IsTracking(textDocument.GetURI())) - { - context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return null; - } - - return textDocument; - } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 648dd3e0dc428..95792e82c09c4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -26,10 +26,37 @@ internal abstract class AbstractDocumentPullDiagnosticHandler> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { + if (GetOpenDocument(context) is null) + return new([]); + return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); } - public abstract TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + protected static TextDocument? GetOpenDocument(RequestContext context) + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return null; + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return null; + } + + return textDocument; + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 0316bd9b08909..caef56d8d2669 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -19,14 +19,6 @@ internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); - protected static bool ShouldIgnoreContext(RequestContext context) - { - // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace - // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for - // document-diagnostics instead. - return context.ServerKind == WellKnownLspServerKinds.RazorLspServer; - } - protected static IEnumerable GetProjectsInPriorityOrder( Solution solution, ImmutableArray supportedLanguages) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 6b6fee7538432..72ee8b0268d84 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -53,6 +53,14 @@ public void Dispose() protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { + if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) + { + // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace + // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for + // document-diagnostics instead. + return new([]); + } + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, false, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index d255fd0fc6f97..dffea4db7b4ae 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -53,6 +53,7 @@ public async ValueTask> CreateDiagnosticSource if (providersDictionary.TryGetValue(sourceName, out var provider)) return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + context.TraceInformation($"No `{sourceName}` diagnostics for {isDocument}"); return []; } else @@ -83,7 +84,7 @@ public async ValueTask> CreateDiagnosticSource } else { - // For workspace we need to group sources by document and IsLiveSource + // For workspace we need to group sources by source id and IsLiveSource sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) .ToImmutableArray(); @@ -93,10 +94,8 @@ public async ValueTask> CreateDiagnosticSource } } - private class AggregatedDocumentDiagnosticSource : IDiagnosticSource + private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource { - private readonly ImmutableArray _sources; - public static ImmutableArray AggregateIfNeeded(IEnumerable sources) { var result = sources.ToImmutableArray(); @@ -108,19 +107,16 @@ public static ImmutableArray AggregateIfNeeded(IEnumerable sources) - => this._sources = sources; - public bool IsLiveSource() => true; - public Project GetProject() => _sources[0].GetProject(); - public ProjectOrDocumentId GetId() => _sources[0].GetId(); - public TextDocumentIdentifier? GetDocumentIdentifier() => _sources[0].GetDocumentIdentifier(); - public string ToDisplayString() => $"{this.GetType().Name}: count={_sources.Length}"; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var source in _sources) + foreach (var source in sources) { var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); diagnostics.AddRange(namedDiagnostics); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index 436465a8f351d..5f83473c67c8e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -26,10 +26,10 @@ public AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(IDiagnosticAnal public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (GetOpenDocument(context) is not TextDocument textDocument) + if (context.TextDocument is null) return new([]); - var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, textDocument); + var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, context.TextDocument); return new([source]); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs index c020ee7e30e81..d721d1674e65d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -20,10 +20,7 @@ internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptio { public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (GetOpenDocument(context) is not TextDocument textDocument) - return new([]); - - if (textDocument is not Document document) + if (context.TextDocument is not Document document) { context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); return new([]); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 876f058999c8d..b8121d1aa09d5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -26,15 +26,14 @@ internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - var textDocument = GetOpenDocument(context); - if (textDocument is null) - return new([]); - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + if (context.TextDocument == null || + globalOptions.GetBackgroundAnalysisScope(context.TextDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + { return new([]); + } - return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); + return new([new NonLocalDocumentDiagnosticSource(context.TextDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index a87be284174f0..5225529cb2d9f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public @@ -14,7 +13,7 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitial { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); var regParams = new RegistrationParams @@ -30,23 +29,14 @@ Registration FromSourceName(string sourceName) { return new() { + // Due to https://github.com/microsoft/language-server-protocol/issues/1723 + // we need to use textDocument/diagnostic instead of workspace/diagnostic + Method = Methods.TextDocumentDiagnosticName, Id = sourceName, - Method = Methods.WorkspaceDiagnosticName, RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkDoneProgress = true } }; } } - - bool IsFsaEnabled() - { - foreach (var language in context.SupportedLanguages) - { - if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) - return true; - } - - return false; - } } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 38e94f79aceaa..2a13c533b9094 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -45,73 +45,68 @@ internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( /// public override async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (!ShouldIgnoreContext(context)) - { - Contract.ThrowIfNull(context.Solution); + Contract.ThrowIfNull(context.Solution); - using var _ = ArrayBuilder.GetInstance(out var result); + using var _ = ArrayBuilder.GetInstance(out var result); - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + return result.ToImmutableAndClear(); - async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); - return; + return; - void AddDocumentSources(IEnumerable documents) + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) { - foreach (var document in documents) + if (!ShouldSkipDocument(context, document)) { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); } } + } - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); } - } - return []; + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs index cb8ff971b769b..e47e52bc8bc60 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -20,12 +20,7 @@ internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : Abstr { public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (!ShouldIgnoreContext(context)) - { - Contract.ThrowIfNull(context.Solution); - return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); - } - - return new([]); + Contract.ThrowIfNull(context.Solution); + return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs index 32b09034eebe2..33755784ebd49 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs @@ -23,25 +23,22 @@ internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOpti { public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (!ShouldIgnoreContext(context)) - { - Contract.ThrowIfNull(context.Solution); + Contract.ThrowIfNull(context.Solution); - // Only compute task list items for closed files if the option is on for it. - if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + // Only compute task list items for closed files if the option is on for it. + if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + { + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) { - using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + foreach (var document in project.Documents) { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } + if (!ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); } - - return new(result.ToImmutableAndClear()); } + + return new(result.ToImmutableAndClear()); } return new([]); From 79940f1753902200d56a3af40dd889fabbad85b9 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 24 Apr 2024 17:59:20 +0200 Subject: [PATCH 0819/1047] [17.8] Migrate official builds to 1ES template (#73210) * Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.23461.2 -> To Version 8.0.0-beta.24204.3 Dependency coherency updates Microsoft.DotNet.XliffTasks From Version 1.0.0-beta.23426.1 -> To Version 1.0.0-beta.23475.1 (parent: Microsoft.DotNet.Arcade.Sdk * Revert SDK change * Update source build baseline * Bump structured log viewer library version * [17.8] Migrate official builds to 1ES template * Update path to OneOffInsertion.ps1 --------- Co-authored-by: dotnet-maestro[bot] --- azure-pipelines-official.yml | 645 +++++++++--------- eng/SourceBuildPrebuiltBaseline.xml | 8 + eng/Version.Details.xml | 12 +- eng/Versions.props | 2 +- eng/common/SetupNugetSources.ps1 | 26 +- eng/common/darc-init.ps1 | 2 +- eng/common/darc-init.sh | 2 +- eng/common/native/init-compiler.sh | 2 +- .../post-build/add-build-to-channel.ps1 | 2 +- eng/common/post-build/publish-using-darc.ps1 | 6 +- .../post-build/trigger-subscriptions.ps1 | 2 +- eng/common/sdk-task.ps1 | 2 +- eng/common/templates-official/job/job.yml | 264 +++++++ .../templates-official/job/onelocbuild.yml | 112 +++ .../job/publish-build-assets.yml | 155 +++++ .../templates-official/job/source-build.yml | 67 ++ .../job/source-index-stage1.yml | 68 ++ .../templates-official/jobs/codeql-build.yml | 31 + eng/common/templates-official/jobs/jobs.yml | 97 +++ .../templates-official/jobs/source-build.yml | 46 ++ .../post-build/common-variables.yml | 22 + .../post-build/post-build.yml | 285 ++++++++ .../post-build/setup-maestro-vars.yml | 70 ++ .../post-build/trigger-subscription.yml | 13 + .../steps/add-build-to-channel.yml | 13 + .../templates-official/steps/build-reason.yml | 12 + .../steps/component-governance.yml | 13 + .../steps/execute-codeql.yml | 32 + .../templates-official/steps/execute-sdl.yml | 88 +++ .../steps/generate-sbom.yml | 48 ++ .../templates-official/steps/publish-logs.yml | 23 + .../templates-official/steps/retain-build.yml | 28 + .../steps/send-to-helix.yml | 91 +++ .../templates-official/steps/source-build.yml | 129 ++++ .../variables/pool-providers.yml | 45 ++ .../variables/sdl-variables.yml | 7 + eng/common/templates/job/job.yml | 6 +- .../templates/job/publish-build-assets.yml | 18 +- .../templates/post-build/common-variables.yml | 2 +- .../templates/post-build/post-build.yml | 20 +- .../templates/steps/component-governance.yml | 2 +- eng/common/templates/steps/generate-sbom.yml | 2 +- .../templates/variables/pool-providers.yml | 12 +- eng/common/tools.ps1 | 16 +- eng/common/tools.sh | 7 +- eng/pipelines/insert.yml | 2 +- global.json | 4 +- 47 files changed, 2163 insertions(+), 398 deletions(-) create mode 100644 eng/common/templates-official/job/job.yml create mode 100644 eng/common/templates-official/job/onelocbuild.yml create mode 100644 eng/common/templates-official/job/publish-build-assets.yml create mode 100644 eng/common/templates-official/job/source-build.yml create mode 100644 eng/common/templates-official/job/source-index-stage1.yml create mode 100644 eng/common/templates-official/jobs/codeql-build.yml create mode 100644 eng/common/templates-official/jobs/jobs.yml create mode 100644 eng/common/templates-official/jobs/source-build.yml create mode 100644 eng/common/templates-official/post-build/common-variables.yml create mode 100644 eng/common/templates-official/post-build/post-build.yml create mode 100644 eng/common/templates-official/post-build/setup-maestro-vars.yml create mode 100644 eng/common/templates-official/post-build/trigger-subscription.yml create mode 100644 eng/common/templates-official/steps/add-build-to-channel.yml create mode 100644 eng/common/templates-official/steps/build-reason.yml create mode 100644 eng/common/templates-official/steps/component-governance.yml create mode 100644 eng/common/templates-official/steps/execute-codeql.yml create mode 100644 eng/common/templates-official/steps/execute-sdl.yml create mode 100644 eng/common/templates-official/steps/generate-sbom.yml create mode 100644 eng/common/templates-official/steps/publish-logs.yml create mode 100644 eng/common/templates-official/steps/retain-build.yml create mode 100644 eng/common/templates-official/steps/send-to-helix.yml create mode 100644 eng/common/templates-official/steps/source-build.yml create mode 100644 eng/common/templates-official/variables/pool-providers.yml create mode 100644 eng/common/templates-official/variables/sdl-variables.yml diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 95b3f247cfafd..5f681633869f5 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -11,8 +11,11 @@ trigger: pr: none resources: -- repo: self - clean: true + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release parameters: - name: IbcDrop @@ -42,20 +45,18 @@ 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 - # Get $dotnetfeed-storage-access-key-1 from DotNet-Blob-Feed - # Get $microsoft-symbol-server-pat and $symweb-symbol-server-pat from DotNet-Symbol-Server-Pats # Get $AccessToken-dotnet-build-bot-public-repo from DotNet-Versions-Publish - - group: DotNet-Blob-Feed - - group: DotNet-Symbol-Server-Pats - group: DotNet-Versions-Publish - group: DotNet-VSTS-Infra-Access - group: DotNet-DevDiv-Insertion-Workflow-Variables - name: _DevDivDropAccessToken value: $(dn-bot-devdiv-drop-rw-code-rw) + - name: ArtifactServices.Drop.PAT + value: $(dn-bot-devdiv-drop-rw-code-rw) - group: DotNet-Roslyn-Insertion-Variables - name: BuildConfiguration @@ -76,331 +77,311 @@ variables: - name: Insertion.TitleSuffix value: '' -stages: - -- stage: build - displayName: Build and Test - pool: - name: NetCore1ESPool-Svc-Internal - demands: ImageOverride -equals windows.vs2022preview.amd64 - - jobs: - - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/release/dev17.9') }}: - - template: /eng/common/templates/job/onelocbuild.yml - parameters: - MirrorRepo: roslyn - MirrorBranch: release/dev17.9 - LclSource: lclFilesfromPackage - LclPackageId: 'LCL-JUNO-PROD-ROSLYN' - - - template: eng/common/templates/jobs/source-build.yml - - - job: OfficialBuild - displayName: Official Build - timeoutInMinutes: 360 - - steps: - - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" - displayName: Setting SourceBranchName variable - condition: succeeded() - - - task: Powershell@2 - displayName: Tag official build - inputs: - targetType: inline - script: | - Write-Host "##vso[build.addBuildTag]OfficialBuild" - condition: succeeded() - - # Don't run this while we don't have a main-vs-deps to merge. Should be uncommented when the branch comes back. Also need to change the condition of the tagging task above. - # - # - task: Powershell@2 - # displayName: Tag main validation build - # inputs: - # targetType: inline - # script: | - # Write-Host "##vso[build.addBuildTag]MainValidationBuild" - # condition: and(succeeded(), eq(variables['SourceBranchName'], 'main')) - - # Don't run this while we don't have a main-vs-deps to merge. Should be uncommented when the branch comes back. - # - # - task: PowerShell@2 - # displayName: Merge main-vs-deps into source branch - # inputs: - # filePath: 'scripts\merge-vs-deps.ps1' - # arguments: '-accessToken $(dn-bot-dnceng-build-rw-code-rw)' - # condition: and(succeeded(), eq(variables['SourceBranchName'], 'main')) - - - powershell: Write-Host "##vso[task.setvariable variable=VisualStudio.DropName]Products/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)" - displayName: Setting VisualStudio.DropName variable - - - task: NodeTool@0 - inputs: - versionSpec: '16.x' - displayName: 'Install Node.js' - - - - task: NuGetToolInstaller@0 - inputs: - versionSpec: '4.9.2' - - # Authenticate with service connections to be able to publish packages to external nuget feeds. - - task: NuGetAuthenticate@0 - inputs: - nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk, devdiv/engineering, devdiv/dotnet-core-internal-tooling - - # Needed for SBOM tool - - task: UseDotNet@2 - displayName: 'Use .NET Core 3.1 runtime' - inputs: - packageType: runtime - version: 3.1.28 - installationPath: '$(Build.SourcesDirectory)\.dotnet' - - # Needed because the build fails the NuGet Tools restore without it - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - packageType: sdk - useGlobalJson: true - workingDirectory: '$(Build.SourcesDirectory)' - - # Needed to restore the Microsoft.DevDiv.Optimization.Data.PowerShell package - - task: NuGetCommand@2 - displayName: Restore internal tools - inputs: - command: restore - feedsToUse: config - restoreSolution: 'eng\common\internal\Tools.csproj' - nugetConfigPath: 'NuGet.config' - restoreDirectory: '$(Build.SourcesDirectory)\.packages' - - - task: MicroBuildSigningPlugin@2 - inputs: - signType: $(SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - condition: and(succeeded(), in(variables['SignType'], 'test', 'real')) - - - task: PowerShell@2 - displayName: Build - inputs: - filePath: eng/build.ps1 - arguments: -ci - -restore - -build - -pack - -sign - -publish - -binaryLog - -configuration $(BuildConfiguration) - -officialBuildId $(Build.BuildNumber) - -officialSkipTests $(SkipTests) - -officialSkipApplyOptimizationData $(SkipApplyOptimizationData) - -officialSourceBranchName $(SourceBranchName) - -officialIbcDrop $(IbcDrop) - -officialVisualStudioDropAccessToken $(_DevDivDropAccessToken) - /p:RepositoryName=$(Build.Repository.Name) - /p:VisualStudioDropName=$(VisualStudio.DropName) - /p:DotNetSignType=$(SignType) - /p:DotNetPublishToBlobFeed=true - /p:DotNetPublishBlobFeedKey=$(dotnetfeed-storage-access-key-1) - /p:DotNetPublishBlobFeedUrl=https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - /p:PublishToSymbolServer=true - /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) - /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) - /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) - /p:DotnetPublishUsingPipelines=true - /p:IgnoreIbcMergeErrors=true - /p:GenerateSbom=true - condition: succeeded() - - - template: eng\common\templates\steps\generate-sbom.yml - - - task: PowerShell@2 - displayName: Publish Assets - inputs: - filePath: 'eng\publish-assets.ps1' - arguments: '-configuration $(BuildConfiguration) -branchName "$(SourceBranchName)"' - condition: succeeded() - - # Publish OptProf configuration files - # The env variable is required to enable cross account access using PAT (dnceng -> devdiv) - - task: ms-vscs-artifact.build-tasks.artifactDropTask-1.artifactDropTask@0 - env: - ARTIFACTSERVICES_DROP_PAT: $(_DevDivDropAccessToken) - inputs: - dropServiceURI: 'https://devdiv.artifacts.visualstudio.com' - buildNumber: 'ProfilingInputs/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)' - sourcePath: '$(Build.SourcesDirectory)\artifacts\OptProf\$(BuildConfiguration)\Data' - toLowerCase: false - usePat: false - retentionDays: 90 - displayName: 'OptProf - Publish to Artifact Services - ProfilingInputs' - condition: succeeded() - - # Publish OptProf generated JSON files as a build artifact. This allows for easy inspection from - # a build execution. - - task: PublishBuildArtifacts@1 - displayName: Publish OptProf Data Files - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\artifacts\OptProf\$(BuildConfiguration)\Data' - ArtifactName: 'OptProf Data Files' - condition: succeeded() - - - task: PublishBuildArtifacts@1 - displayName: Publish Logs - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)' - ArtifactName: 'Build Diagnostic Files' - publishLocation: Container - continueOnError: true - condition: succeededOrFailed() - - - task: PublishBuildArtifacts@1 - displayName: Publish Ngen Logs - inputs: - PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)\ngen' - ArtifactName: 'NGen Logs' - publishLocation: Container - continueOnError: true - condition: succeeded() - - - task: PublishTestResults@2 - displayName: Publish xUnit Test Results - inputs: - testRunner: XUnit - testResultsFiles: '$(Build.SourcesDirectory)\artifacts\TestResults\$(BuildConfiguration)\*.xml' - mergeTestResults: true - testRunTitle: 'Unit Tests' - condition: and(succeededOrFailed(), ne(variables['SkipTests'], 'true')) - - # Publishes setup VSIXes to a drop. - # Note: The insertion tool looks for the display name of this task in the logs. - - task: ms-vseng.MicroBuildTasks.4305a8de-ba66-4d8b-b2d1-0dc4ecbbf5e8.MicroBuildUploadVstsDropFolder@1 - displayName: Upload VSTS Drop - inputs: - DropName: $(VisualStudio.DropName) - DropFolder: 'artifacts\VSSetup\$(BuildConfiguration)\Insertion' - AccessToken: $(_DevDivDropAccessToken) - condition: succeeded() - - # Publish insertion packages to CoreXT store. - - task: NuGetCommand@2 - displayName: Publish CoreXT Packages - inputs: - command: push - packagesToPush: '$(Build.SourcesDirectory)\artifacts\VSSetup\$(BuildConfiguration)\DevDivPackages\**\*.nupkg' - allowPackageConflicts: true - nuGetFeedType: external - publishFeedCredentials: 'DevDiv - VS package feed' - condition: succeeded() - - # Publish an artifact that the RoslynInsertionTool is able to find by its name. - - task: PublishBuildArtifacts@1 - displayName: Publish Artifact VSSetup - inputs: - PathtoPublish: 'artifacts\VSSetup\$(BuildConfiguration)' - ArtifactName: 'VSSetup' - condition: succeeded() - - # Publish our NuPkgs as an artifact. The name of this artifact must be PackageArtifacts as the - # arcade templates depend on the name. - - task: PublishBuildArtifacts@1 - displayName: Publish Artifact Packages - inputs: - PathtoPublish: 'artifacts\packages\$(BuildConfiguration)' - ArtifactName: 'PackageArtifacts' - condition: succeeded() - - # Publish language server package - - powershell: Write-Host "##vso[task.setvariable variable=NPMFileName]$((ls -file $(Build.SourcesDirectory)\artifacts\packages\Release\NPM\ | select -First 1).FullName)" - displayName: Setting NPM Package Variable - - # Authenticates the .npmrc file for publishing to the internal AzDO feed. - # See: https://learn.microsoft.com/azure/devops/pipelines/tasks/package/npm-authenticate?view=azure-devops - - task: npmAuthenticate@0 - displayName: Authenticate NPM Feed - inputs: - workingFile: $(Build.SourcesDirectory)/src/VisualStudio/DevKit/Impl/.npmrc - customEndpoint: devdiv-vs-green-npm-package-feed - - - script: npm publish --userconfig $(Build.SourcesDirectory)\src\VisualStudio\DevKit\Impl\.npmrc $(NPMFileName) - displayName: Publish Language Server NPM Package - - # Publish Asset Manifests for Build Asset Registry job - - task: PublishBuildArtifacts@1 - displayName: Publish Asset Manifests - inputs: - PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(BuildConfiguration)/AssetManifest' - ArtifactName: AssetManifests - condition: succeeded() - - - task: ms-vseng.MicroBuildTasks.521a94ea-9e68-468a-8167-6dcf361ea776.MicroBuildCleanup@1 - displayName: Perform Cleanup Tasks - condition: succeededOrFailed() - - # Publish to Build Asset Registry - - template: /eng/common/templates/job/publish-build-assets.yml - parameters: - publishUsingPipelines: true - dependsOn: - - OfficialBuild - pool: +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + featureFlags: + autoBaseline: true + sdl: + sourceAnalysisPool: name: NetCore1ESPool-Svc-Internal - demands: ImageOverride -equals windows.vs2022.amd64 - -- stage: insert - dependsOn: - - publish_using_darc - displayName: Insert to VS - - jobs: - - job: insert - displayName: Insert to VS + image: 1es-windows-2022 + os: windows + sbom: + enabled: false + suppression: + suppressionFile: $(Build.SourcesDirectory)\eng\config\guardian\.gdnsuppres pool: name: NetCore1ESPool-Svc-Internal - demands: ImageOverride -equals windows.vs2022.amd64 - steps: - - download: current - artifact: VSSetup - - powershell: | - $branchName = "$(Build.SourceBranch)".Substring("refs/heads/".Length) - Write-Host "##vso[task.setvariable variable=ComponentBranchName]$branchName" - displayName: Get Branch Name - - template: eng/pipelines/insert.yml - parameters: - buildUserName: "dn-bot@microsoft.com" - buildPassword: $(dn-bot-devdiv-build-e-code-full-release-e-packaging-r) - componentUserName: "dn-bot@microsoft.com" - componentPassword: $(dn-bot-dnceng-build-e-code-full-release-e-packaging-r) - componentBuildProjectName: internal - sourceBranch: "$(ComponentBranchName)" - publishDataURI: "https://dev.azure.com/dnceng/internal/_apis/git/repositories/dotnet-roslyn/items?path=eng/config/PublishData.json&api-version=6.0" - publishDataAccessToken: "$(System.AccessToken)" - dropPath: '$(Pipeline.Workspace)\VSSetup' - -- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - template: eng\common\templates\post-build\post-build.yml - parameters: - publishingInfraVersion: 3 - # Symbol validation is not entirely reliable as of yet, so should be turned off until - # https://github.com/dotnet/arcade/issues/2871 is resolved. - enableSymbolValidation: false - enableSourceLinkValidation: false - # Enable SDL validation, passing through values from the 'DotNet-Roslyn-SDLValidation-Params' group. - SDLValidationParameters: - enable: true - params: >- - -SourceToolsList @("policheck","credscan") - -ArtifactToolsList @("binskim") - -BinskimAdditionalRunConfigParams @("IgnorePdbLoadError < True","Recurse < True","SymbolsPath < SRV*https://msdl.microsoft.com/download/symbols") - -TsaInstanceURL $(_TsaInstanceURL) - -TsaProjectName $(_TsaProjectName) - -TsaNotificationEmail $(_TsaNotificationEmail) - -TsaCodebaseAdmin $(_TsaCodebaseAdmin) - -TsaBugAreaPath $(_TsaBugAreaPath) - -TsaIterationPath $(_TsaIterationPath) - -TsaRepositoryName $(_TsaRepositoryName) - -TsaCodebaseName $(_TsaCodebaseName) - -TsaPublish $True + image: windows.vs2022preview.amd64 + os: windows + customBuildTags: + - ES365AIMigrationTooling + stages: + + - stage: build + displayName: Build and Test + + jobs: + - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/release/dev17.10') }}: + - template: /eng/common/templates-official/job/onelocbuild.yml@self + parameters: + MirrorRepo: roslyn + MirrorBranch: release/dev17.10 + LclSource: lclFilesfromPackage + LclPackageId: 'LCL-JUNO-PROD-ROSLYN' + + - template: /eng/common/templates-official/jobs/source-build.yml@self + + - job: OfficialBuild + displayName: Official Build + timeoutInMinutes: 360 + templateContext: + outputs: + + # Publish OptProf generated JSON files as a pipeline artifact. This allows for easy inspection from + # a build execution. + - output: pipelineArtifact + displayName: 'Publish OptProf Data Files' + condition: succeeded() + targetPath: '$(Build.SourcesDirectory)\artifacts\OptProf\$(BuildConfiguration)\Data' + artifactName: 'OptProf Data Files' + + - output: pipelineArtifact + displayName: 'Publish Logs' + condition: succeededOrFailed() + targetPath: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)' + artifactName: 'Build Diagnostic Files' + publishLocation: Container + + - output: pipelineArtifact + displayName: 'Publish Ngen Logs' + condition: succeeded() + targetPath: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)\ngen' + artifactName: 'NGen Logs' + publishLocation: Container + + # Publishes setup VSIXes to a drop. + # Note: The insertion tool looks for the display name of this task in the logs. + - output: microBuildVstsDrop + displayName: Upload VSTS Drop + condition: succeeded() + dropFolder: 'artifacts\VSSetup\$(BuildConfiguration)\Insertion' + dropName: $(VisualStudio.DropName) + accessToken: $(_DevDivDropAccessToken) + + # Publish insertion packages to CoreXT store. + - output: nuget + displayName: 'Publish CoreXT Packages' + condition: succeeded() + packageParentPath: '$(Build.SourcesDirectory)\artifacts\VSSetup\$(BuildConfiguration)\DevDivPackages' + packagesToPush: '$(Build.SourcesDirectory)\artifacts\VSSetup\$(BuildConfiguration)\DevDivPackages\**\*.nupkg' + allowPackageConflicts: true + nuGetFeedType: external + publishFeedCredentials: 'DevDiv - VS package feed' + + # Publish an artifact that the RoslynInsertionTool is able to find by its name. + - output: pipelineArtifact + displayName: 'Publish Artifact VSSetup' + condition: succeeded() + targetPath: 'artifacts\VSSetup\$(BuildConfiguration)' + artifactName: 'VSSetup' + + # Publish our NuPkgs as an artifact. The name of this artifact must be PackageArtifacts as the + # arcade templates depend on the name. + - output: buildArtifacts + displayName: 'Publish Artifact Packages' + condition: succeeded() + PathtoPublish: 'artifacts\packages\$(BuildConfiguration)' + ArtifactName: 'PackageArtifacts' + + # Publish Asset Manifests for Build Asset Registry job + - output: buildArtifacts + displayName: 'Publish Asset Manifests' + condition: succeeded() + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(BuildConfiguration)/AssetManifest' + ArtifactName: AssetManifests + + steps: + - powershell: Write-Host "##vso[task.setvariable variable=SourceBranchName]$('$(Build.SourceBranch)'.Substring('refs/heads/'.Length))" + displayName: Setting SourceBranchName variable + condition: succeeded() + + - task: Powershell@2 + displayName: Tag official build + inputs: + targetType: inline + script: | + Write-Host "##vso[build.addBuildTag]OfficialBuild" + condition: succeeded() + + # Don't run this while we don't have a main-vs-deps to merge. Should be uncommented when the branch comes back. Also need to change the condition of the tagging task above. + # + # - task: Powershell@2 + # displayName: Tag main validation build + # inputs: + # targetType: inline + # script: | + # Write-Host "##vso[build.addBuildTag]MainValidationBuild" + # condition: and(succeeded(), eq(variables['SourceBranchName'], 'main')) + + # Don't run this while we don't have a main-vs-deps to merge. Should be uncommented when the branch comes back. + # + # - task: PowerShell@2 + # displayName: Merge main-vs-deps into source branch + # inputs: + # filePath: 'scripts\merge-vs-deps.ps1' + # arguments: '-accessToken $(dn-bot-dnceng-build-rw-code-rw)' + # condition: and(succeeded(), eq(variables['SourceBranchName'], 'main')) + + - powershell: Write-Host "##vso[task.setvariable variable=VisualStudio.DropName]Products/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)" + displayName: Setting VisualStudio.DropName variable + + - task: NodeTool@0 + inputs: + versionSpec: '16.x' + displayName: 'Install Node.js' + + - task: NuGetToolInstaller@0 + inputs: + versionSpec: '4.9.2' + + # Authenticate with service connections to be able to publish packages to external nuget feeds. + - task: NuGetAuthenticate@1 + inputs: + nuGetServiceConnections: azure-public/vs-impl, azure-public/vssdk, devdiv/engineering, devdiv/dotnet-core-internal-tooling + + # Needed for SBOM tool + - task: UseDotNet@2 + displayName: 'Use .NET Core 3.1 runtime' + inputs: + packageType: runtime + version: 3.1.28 + installationPath: '$(Build.SourcesDirectory)\.dotnet' + + # Needed because the build fails the NuGet Tools restore without it + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + useGlobalJson: true + workingDirectory: '$(Build.SourcesDirectory)' + + # Needed to restore the Microsoft.DevDiv.Optimization.Data.PowerShell package + - task: NuGetCommand@2 + displayName: Restore internal tools + inputs: + command: restore + feedsToUse: config + restoreSolution: 'eng\common\internal\Tools.csproj' + nugetConfigPath: 'NuGet.config' + restoreDirectory: '$(Build.SourcesDirectory)\.packages' + + - task: MicroBuildSigningPlugin@4 + inputs: + signType: $(SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + condition: and(succeeded(), in(variables['SignType'], 'test', 'real')) + + - task: PowerShell@2 + displayName: Build + inputs: + filePath: eng/build.ps1 + arguments: -ci + -prepareMachine + -restore + -build + -pack + -sign + -publish + -binaryLog + -configuration $(BuildConfiguration) + -officialBuildId $(Build.BuildNumber) + -officialSkipTests $(SkipTests) + -officialSkipApplyOptimizationData $(SkipApplyOptimizationData) + -officialSourceBranchName $(SourceBranchName) + -officialIbcDrop $(IbcDrop) + -officialVisualStudioDropAccessToken $(_DevDivDropAccessToken) + /p:RepositoryName=$(Build.Repository.Name) + /p:VisualStudioDropName=$(VisualStudio.DropName) + /p:DotNetSignType=$(SignType) + /p:PublishToSymbolServer=true + /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) + /p:DotnetPublishUsingPipelines=true + /p:IgnoreIbcMergeErrors=true + /p:GenerateSbom=true + condition: succeeded() + + - template: /eng/common/templates-official/steps/generate-sbom.yml@self + + - task: PowerShell@2 + displayName: Publish Assets + inputs: + filePath: 'eng\publish-assets.ps1' + arguments: '-configuration $(BuildConfiguration) -branchName "$(SourceBranchName)"' + condition: succeeded() + + # Publish OptProf configuration files to the artifact service + # This uses the ArtifactServices.Drop.PAT build variable which is required to enable cross account access using PAT (dnceng -> devdiv) + - task: 1ES.PublishArtifactsDrop@1 + inputs: + dropServiceURI: 'https://devdiv.artifacts.visualstudio.com' + buildNumber: 'ProfilingInputs/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)' + sourcePath: '$(Build.SourcesDirectory)\artifacts\OptProf\$(BuildConfiguration)\Data' + toLowerCase: false + usePat: false + retentionDays: 90 + displayName: 'OptProf - Publish to Artifact Services - ProfilingInputs' + condition: succeeded() + + - task: PublishTestResults@2 + displayName: Publish xUnit Test Results + inputs: + testRunner: XUnit + testResultsFiles: '$(Build.SourcesDirectory)\artifacts\TestResults\$(BuildConfiguration)\*.xml' + mergeTestResults: true + testRunTitle: 'Unit Tests' + condition: and(succeededOrFailed(), ne(variables['SkipTests'], 'true')) + + # Publish to Build Asset Registry + - template: /eng/common/templates-official/job/publish-build-assets.yml@self + parameters: + publishUsingPipelines: true + dependsOn: + - OfficialBuild + pool: + name: NetCore1ESPool-Svc-Internal + demands: ImageOverride -equals windows.vs2022.amd64 + + - stage: insert + dependsOn: + - publish_using_darc + displayName: Insert to VS + + jobs: + - job: insert + displayName: Insert to VS + steps: + - download: current + artifact: VSSetup + - powershell: | + $branchName = "$(Build.SourceBranch)".Substring("refs/heads/".Length) + Write-Host "##vso[task.setvariable variable=ComponentBranchName]$branchName" + displayName: Get Branch Name + - template: /eng/pipelines/insert.yml@self + parameters: + buildUserName: "dn-bot@microsoft.com" + buildPassword: $(dn-bot-devdiv-build-e-code-full-release-e-packaging-r) + componentUserName: "dn-bot@microsoft.com" + componentPassword: $(dn-bot-dnceng-build-e-code-full-release-e-packaging-r) + componentBuildProjectName: internal + sourceBranch: "$(ComponentBranchName)" + publishDataURI: "https://dev.azure.com/dnceng/internal/_apis/git/repositories/dotnet-roslyn/items?path=eng/config/PublishData.json&api-version=6.0" + publishDataAccessToken: "$(System.AccessToken)" + dropPath: '$(Pipeline.Workspace)\VSSetup' + + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: /eng/common/templates-official/post-build/post-build.yml@self + parameters: + publishingInfraVersion: 3 + # Symbol validation is not entirely reliable as of yet, so should be turned off until + # https://github.com/dotnet/arcade/issues/2871 is resolved. + enableSymbolValidation: false + enableSourceLinkValidation: false + # Enable SDL validation, passing through values from the 'DotNet-Roslyn-SDLValidation-Params' group. + SDLValidationParameters: + enable: true + params: >- + -SourceToolsList @("policheck","credscan") + -ArtifactToolsList @("binskim") + -BinskimAdditionalRunConfigParams @("IgnorePdbLoadError < True","Recurse < True","SymbolsPath < SRV*https://msdl.microsoft.com/download/symbols") + -TsaInstanceURL $(_TsaInstanceURL) + -TsaProjectName $(_TsaProjectName) + -TsaNotificationEmail $(_TsaNotificationEmail) + -TsaCodebaseAdmin $(_TsaCodebaseAdmin) + -TsaBugAreaPath $(_TsaBugAreaPath) + -TsaIterationPath $(_TsaIterationPath) + -TsaRepositoryName $(_TsaRepositoryName) + -TsaCodebaseName $(_TsaCodebaseName) + -TsaPublish $True diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 9367f254378e1..bf6d9299a79e6 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -23,4 +23,12 @@ prebuilt only for the repo CI build but not full source-build. --> + + + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f43852927957a..640d936dfe33e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -27,14 +27,14 @@ - + https://github.com/dotnet/arcade - 4a908c64757841121e67fda7adaf776c93893163 + 188340e12c0a372b1681ad6a5e72c608021efdba - + https://github.com/dotnet/xliff-tasks - 194f32828726c3f1f63f79f3dc09b9e99c157b11 + 73f0850939d96131c28cf6ea6ee5aacb4da0083a @@ -50,9 +50,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 4a908c64757841121e67fda7adaf776c93893163 + 188340e12c0a372b1681ad6a5e72c608021efdba https://github.com/dotnet/roslyn-analyzers diff --git a/eng/Versions.props b/eng/Versions.props index daa249543e59c..62d569171ed46 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -209,7 +209,7 @@ 17.1.11-preview-0002 4.3.0 5.0.0 - 2.2.100 + 2.2.206 0.1.0 6.6.0.161 4.10.1 diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 6c65e81925f2a..efa2fd72bfaa2 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -35,7 +35,7 @@ Set-StrictMode -Version 2.0 . $PSScriptRoot\tools.ps1 # Add source entry to PackageSources -function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $Password) { +function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $pwd) { $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") if ($packageSource -eq $null) @@ -48,12 +48,11 @@ function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Usern else { Write-Host "Package source $SourceName already present." } - - AddCredential -Creds $creds -Source $SourceName -Username $Username -Password $Password + AddCredential -Creds $creds -Source $SourceName -Username $Username -pwd $pwd } # Add a credential node for the specified source -function AddCredential($creds, $source, $username, $password) { +function AddCredential($creds, $source, $username, $pwd) { # Looks for credential configuration for the given SourceName. Create it if none is found. $sourceElement = $creds.SelectSingleNode($Source) if ($sourceElement -eq $null) @@ -82,17 +81,18 @@ function AddCredential($creds, $source, $username, $password) { $passwordElement.SetAttribute("key", "ClearTextPassword") $sourceElement.AppendChild($passwordElement) | Out-Null } - $passwordElement.SetAttribute("value", $Password) + + $passwordElement.SetAttribute("value", $pwd) } -function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Password) { +function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $pwd) { $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." ForEach ($PackageSource in $maestroPrivateSources) { Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key - AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -Password $Password + AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -pwd $pwd } } @@ -144,13 +144,13 @@ if ($disabledSources -ne $null) { $userName = "dn-bot" # Insert credential nodes for Maestro's private feeds -InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -Password $Password +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -pwd $Password # 3.1 uses a different feed url format so it's handled differently here $dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") if ($dotnet31Source -ne $null) { - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password } $dotnetVersions = @('5','6','7','8') @@ -159,9 +159,9 @@ foreach ($dotnetVersion in $dotnetVersions) { $feedPrefix = "dotnet" + $dotnetVersion; $dotnetSource = $sources.SelectSingleNode("add[@key='$feedPrefix']") if ($dotnetSource -ne $null) { - AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password - AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal/nuget/v2" -Creds $creds -Username $userName -pwd $Password + AddPackageSource -Sources $sources -SourceName "$feedPrefix-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/$feedPrefix-internal-transport/nuget/v2" -Creds $creds -Username $userName -pwd $Password } } -$doc.Save($filename) +$doc.Save($filename) \ No newline at end of file diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index 435e7641341b1..8fda30bdce2b0 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -1,6 +1,6 @@ param ( $darcVersion = $null, - $versionEndpoint = 'https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16', + $versionEndpoint = 'https://maestro.dot.net/api/assets/darc-version?api-version=2019-01-16', $verbosity = 'minimal', $toolpath = $null ) diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index 84c1d0cc2e75a..c305ae6bd771e 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -2,7 +2,7 @@ source="${BASH_SOURCE[0]}" darcVersion='' -versionEndpoint='https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16' +versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2019-01-16' verbosity='minimal' while [[ $# > 0 ]]; do 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/post-build/add-build-to-channel.ps1 b/eng/common/post-build/add-build-to-channel.ps1 index de2d957922a65..49938f0c89f76 100644 --- a/eng/common/post-build/add-build-to-channel.ps1 +++ b/eng/common/post-build/add-build-to-channel.ps1 @@ -2,7 +2,7 @@ param( [Parameter(Mandatory=$true)][int] $BuildId, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' ) diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1 index 8508397d77640..5a3a32ea8d75b 100644 --- a/eng/common/post-build/publish-using-darc.ps1 +++ b/eng/common/post-build/publish-using-darc.ps1 @@ -3,7 +3,7 @@ param( [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, [Parameter(Mandatory=$true)][string] $AzdoToken, [Parameter(Mandatory=$true)][string] $MaestroToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters @@ -12,7 +12,7 @@ param( try { . $PSScriptRoot\post-build-utils.ps1 - $darc = Get-Darc + $darc = Get-Darc $optionalParams = [System.Collections.ArrayList]::new() @@ -46,7 +46,7 @@ try { } Write-Host 'done.' -} +} catch { Write-Host $_ Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1 index 55dea518ac585..ac9a95778fcd9 100644 --- a/eng/common/post-build/trigger-subscriptions.ps1 +++ b/eng/common/post-build/trigger-subscriptions.ps1 @@ -2,7 +2,7 @@ param( [Parameter(Mandatory=$true)][string] $SourceRepo, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net', [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' ) diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index 6c4ac6fec1a99..73828dd30d317 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -64,7 +64,7 @@ try { $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.6.0-2" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.8.1-2" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml new file mode 100644 index 0000000000000..1f035fee73f4a --- /dev/null +++ b/eng/common/templates-official/job/job.yml @@ -0,0 +1,264 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + condition: '' + container: '' + continueOnError: false + dependsOn: '' + displayName: '' + pool: '' + steps: [] + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' + templateContext: '' + +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishBuildAssets: false + enablePublishTestResults: false + enablePublishUsingPipelines: false + enableBuildRetry: false + disableComponentGovernance: '' + componentGovernanceIgnoreDirectories: '' + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' + name: '' + preSteps: [] + runAsPublic: false +# Sbom related params + enableSbom: true + PackageVersion: 7.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + + variables: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' + # Retry signature validation up to three times, waiting 2 seconds between attempts. + # See https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3028#retry-untrusted-root-failures + - name: NUGET_EXPERIMENTAL_CHAIN_BUILD_RETRY_POLICY + value: 3,2000 + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle template variable syntax + # example: + # - template: path/to/template.yml + # parameters: + # [key]: [value] + - ${{ if ne(variable.template, '') }}: + - template: ${{ variable.template }} + ${{ if ne(variable.parameters, '') }}: + parameters: ${{ variable.parameters }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, ''), eq(variable.template, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + 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')) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: + - task: NuGetAuthenticate@1 + + - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + uploadRichNavArtifacts: ${{ coalesce(parameters.richCodeNavigationUploadArtifacts, false) }} + continueOnError: true + + - template: /eng/common/templates-official/steps/component-governance.yml + parameters: + ${{ if eq(parameters.disableComponentGovernance, '') }}: + ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.runAsPublic, 'false'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/dotnet/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/microsoft/'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}: + disableComponentGovernance: false + ${{ else }}: + disableComponentGovernance: true + ${{ else }}: + disableComponentGovernance: ${{ parameters.disableComponentGovernance }} + componentGovernanceIgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if and(ne(parameters.artifacts.publish.logs, 'false'), ne(parameters.artifacts.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() + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + continueOnError: true + condition: always() + + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: + - task: PublishTestResults@2 + displayName: Publish XUnit Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest'), eq(parameters.enableSbom, 'true')) }}: + - template: /eng/common/templates-official/steps/generate-sbom.yml + parameters: + PackageVersion: ${{ parameters.packageVersion}} + BuildDropPath: ${{ parameters.buildDropPath }} + IgnoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} + + - ${{ if eq(parameters.enableBuildRetry, '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 new file mode 100644 index 0000000000000..52b4d05d3f8dd --- /dev/null +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -0,0 +1,112 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: '' + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + ReusePr: true + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + SkipLocProjectJsonGeneration: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + GitHubOrg: dotnet + MirrorRepo: '' + MirrorBranch: main + condition: '' + JobNameSuffix: '' + +jobs: +- job: OneLocBuild${{ parameters.JobNameSuffix }} + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild${{ parameters.JobNameSuffix }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + - template: /eng/common/templates-official/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + + steps: + - ${{ if ne(parameters.SkipLocProjectJsonGeneration, 'true') }}: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + isShouldReusePrSelected: ${{ parameters.ReusePr }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + ${{ if ne(parameters.MirrorRepo, '') }}: + isMirrorRepoSelected: true + gitHubOrganization: ${{ parameters.GitHubOrg }} + mirrorRepo: ${{ parameters.MirrorRepo }} + mirrorBranch: ${{ parameters.MirrorBranch }} + condition: ${{ parameters.condition }} + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml new file mode 100644 index 0000000000000..589ac80a18b74 --- /dev/null +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -0,0 +1,155 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishAssetsImmediately: false + + artifactsPublishingAdditionalParameters: '' + + signingValidationAdditionalParameters: '' + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + timeoutInMinutes: 150 + + ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + displayName: Publish Assets + ${{ else }}: + displayName: Publish to Build Asset Registry + + variables: + - template: /eng/common/templates-official/variables/pool-providers.yml + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + - name: runCodesignValidationInjection + value: false + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates-official/post-build/common-variables.yml + + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: + 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')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: NuGetAuthenticate@1 + + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:OfficialBuildId=$(Build.BuildNumber) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: powershell@2 + displayName: Create ReleaseConfigs Artifact + inputs: + targetType: inline + script: | + 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' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - task: powershell@2 + displayName: Check if SymbolPublishingExclusionsFile.txt exists + inputs: + targetType: inline + script: | + $symbolExclusionfile = "$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt" + if(Test-Path -Path $symbolExclusionfile) + { + Write-Host "SymbolExclusionFile exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]true" + } + else{ + Write-Host "Symbols Exclusion file does not exists" + Write-Host "##vso[task.setvariable variable=SymbolExclusionFile]false" + } + + - task: 1ES.PublishBuildArtifacts@1 + displayName: Publish SymbolPublishingExclusionsFile Artifact + condition: eq(variables['SymbolExclusionFile'], 'true') + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - ${{ if eq(parameters.publishAssetsImmediately, 'true') }}: + - template: /eng/common/templates-official/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion 3 + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - template: /eng/common/templates-official/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml new file mode 100644 index 0000000000000..f193dfbe23668 --- /dev/null +++ b/eng/common/templates-official/job/source-build.yml @@ -0,0 +1,67 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # Specifies the build script to invoke to perform the build in the repo. The default + # './build.sh' should work for typical Arcade repositories, but this is customizable for + # difficult situations. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + # /eng/common/templates-official/variables/pool-providers.yml can't be used here (some customers declare variables already), so duplicate its logic + 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 + + ${{ 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 + os: linux + + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - template: /eng/common/templates-official/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml new file mode 100644 index 0000000000000..f0513aee5b0da --- /dev/null +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -0,0 +1,68 @@ +parameters: + runAsPublic: false + sourceIndexPackageVersion: 1.0.1-20230228.2 + sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" + preSteps: [] + binlogPath: artifacts/log/Debug/Build.binlog + condition: '' + dependsOn: '' + pool: '' + +jobs: +- job: SourceIndexStage1 + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + variables: + - name: SourceIndexPackageVersion + value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: source-dot-net stage1 variables + - template: /eng/common/templates-official/variables/pool-providers.yml + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + ${{ if eq(parameters.pool, '') }}: + pool: + ${{ if eq(variables['System.TeamProject'], 'public') }}: + name: $(DncEngPublicBuildPool) + demands: ImageOverride -equals windows.vs2019.amd64.open + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + name: $(DncEngInternalBuildPool) + image: windows.vs2022.amd64 + os: windows + + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 6 + inputs: + packageType: sdk + version: 6.0.x + installationPath: $(Agent.TempDirectory)/dotnet + workingDirectory: $(Agent.TempDirectory) + + - script: | + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path $(Agent.TempDirectory)/.source-index/tools + displayName: Download Tools + # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. + workingDirectory: $(Agent.TempDirectory) + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: $(Agent.TempDirectory)/.source-index/tools/BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - script: $(Agent.TempDirectory)/.source-index/tools/UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) + displayName: Upload stage1 artifacts to source index + env: + BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) diff --git a/eng/common/templates-official/jobs/codeql-build.yml b/eng/common/templates-official/jobs/codeql-build.yml new file mode 100644 index 0000000000000..b68d3c2f31990 --- /dev/null +++ b/eng/common/templates-official/jobs/codeql-build.yml @@ -0,0 +1,31 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + +jobs: +- template: /eng/common/templates-official/jobs/jobs.yml + parameters: + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishTestResults: false + enablePublishBuildAssets: false + enablePublishUsingPipelines: false + enableTelemetry: true + + variables: + - group: Publish-Build-Assets + # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in + # sync with the packages.config file. + - name: DefaultGuardianVersion + value: 0.109.0 + - name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + - name: GuardianVersion + value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }} + + jobs: ${{ parameters.jobs }} + diff --git a/eng/common/templates-official/jobs/jobs.yml b/eng/common/templates-official/jobs/jobs.yml new file mode 100644 index 0000000000000..857a0f8ba43e8 --- /dev/null +++ b/eng/common/templates-official/jobs/jobs.yml @@ -0,0 +1,97 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates-official/jobs/source-build.yml for options + sourceBuildParameters: [] + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: Publish the assets as soon as the publish to BAR stage is complete, rather doing so in a separate stage. + publishAssetsImmediately: false + + # Optional: If using publishAssetsImmediately and additional parameters are needed, can be used to send along additional parameters (normally sent to post-build.yml) + artifactsPublishingAdditionalParameters: '' + signingValidationAdditionalParameters: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + enableSourceIndex: false + sourceIndexParams: {} + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates-official/jobs/source-build.yml + parameters: + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml + parameters: + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + publishAssetsImmediately: ${{ parameters.publishAssetsImmediately }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + signingValidationAdditionalParameters: ${{ parameters.signingValidationAdditionalParameters }} diff --git a/eng/common/templates-official/jobs/source-build.yml b/eng/common/templates-official/jobs/source-build.yml new file mode 100644 index 0000000000000..08e5db9bb1161 --- /dev/null +++ b/eng/common/templates-official/jobs/source-build.yml @@ -0,0 +1,46 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates-official/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream8' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates-official/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates-official/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} diff --git a/eng/common/templates-official/post-build/common-variables.yml b/eng/common/templates-official/post-build/common-variables.yml new file mode 100644 index 0000000000000..c24193acfc981 --- /dev/null +++ b/eng/common/templates-official/post-build/common-variables.yml @@ -0,0 +1,22 @@ +variables: + - group: Publish-Build-Assets + + # Whether the build is internal or not + - name: IsInternalBuild + value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} + + # Default Maestro++ API Endpoint and API Version + - name: MaestroApiEndPoint + value: "https://maestro-prod.westus2.cloudapp.azure.com" + - name: MaestroApiAccessToken + value: $(MaestroAccessToken) + - name: MaestroApiVersion + value: "2020-02-20" + + - name: SourceLinkCLIVersion + value: 3.0.0 + - name: SymbolToolVersion + value: 1.0.1 + + - name: runCodesignValidationInjection + value: false diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml new file mode 100644 index 0000000000000..da1f40958b450 --- /dev/null +++ b/eng/common/templates-official/post-build/post-build.yml @@ -0,0 +1,285 @@ +parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V1 is no longer supported + # Publishing V2 is no longer supported + # Publishing V3 is the default + - name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + + - name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + + - name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + + - name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + + - name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + + - name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + + - name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + + - name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + + - name: SDLValidationParameters + type: object + default: + enable: false + publishGdn: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + - name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + + - name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + + - name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + + # Which stages should finish execution before post-build stages start + - name: validateDependsOn + type: object + default: + - build + + - name: publishDependsOn + type: object + default: + - Validate + + # Optional: Call asset publishing rather than running in a separate stage + - name: publishAssetsImmediately + type: boolean + default: false + +stages: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + - template: /eng/common/templates-official/variables/pool-providers.yml + jobs: + - job: + displayName: NuGet Validation + condition: and(succeededOrFailed(), eq( ${{ parameters.enableNugetValidation }}, 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + + - job: + displayName: Signing Validation + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + BinlogToolVersion: $(BinlogToolVersion) + + - job: + displayName: SourceLink Validation + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: $(DncEngInternalBuildPool) + image: 1es-windows-2022 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + +- ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ else }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + - template: /eng/common/templates-official/variables/pool-providers.yml + jobs: + - job: + displayName: Publish Using Darc + timeoutInMinutes: 120 + pool: + # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + name: AzurePipelines-EO + image: 1ESPT-Windows2022 + demands: Cmd + os: windows + # If it's not devdiv, it's dnceng + ${{ else }}: + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 + os: windows + steps: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - task: NuGetAuthenticate@1 + + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish true + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' diff --git a/eng/common/templates-official/post-build/setup-maestro-vars.yml b/eng/common/templates-official/post-build/setup-maestro-vars.yml new file mode 100644 index 0000000000000..0c87f149a4ad7 --- /dev/null +++ b/eng/common/templates-official/post-build/setup-maestro-vars.yml @@ -0,0 +1,70 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + +steps: + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true + + - task: PowerShell@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + targetType: inline + pwsh: true + script: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff --git a/eng/common/templates-official/post-build/trigger-subscription.yml b/eng/common/templates-official/post-build/trigger-subscription.yml new file mode 100644 index 0000000000000..da669030daf6e --- /dev/null +++ b/eng/common/templates-official/post-build/trigger-subscription.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Triggering subscriptions + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/trigger-subscriptions.ps1 + arguments: -SourceRepo $(Build.Repository.Uri) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates-official/steps/add-build-to-channel.yml b/eng/common/templates-official/steps/add-build-to-channel.yml new file mode 100644 index 0000000000000..f67a210d62f3e --- /dev/null +++ b/eng/common/templates-official/steps/add-build-to-channel.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Add Build to Channel + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 + arguments: -BuildId $(BARBuildId) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroApiAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates-official/steps/build-reason.yml b/eng/common/templates-official/steps/build-reason.yml new file mode 100644 index 0000000000000..eba58109b52c9 --- /dev/null +++ b/eng/common/templates-official/steps/build-reason.yml @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/component-governance.yml new file mode 100644 index 0000000000000..cbba0596709da --- /dev/null +++ b/eng/common/templates-official/steps/component-governance.yml @@ -0,0 +1,13 @@ +parameters: + disableComponentGovernance: false + componentGovernanceIgnoreDirectories: '' + +steps: +- ${{ if eq(parameters.disableComponentGovernance, 'true') }}: + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + displayName: Set skipComponentGovernanceDetection variable +- ${{ if ne(parameters.disableComponentGovernance, 'true') }}: + - task: ComponentGovernanceComponentDetection@0 + continueOnError: true + inputs: + ignoreDirectories: ${{ parameters.componentGovernanceIgnoreDirectories }} \ No newline at end of file diff --git a/eng/common/templates-official/steps/execute-codeql.yml b/eng/common/templates-official/steps/execute-codeql.yml new file mode 100644 index 0000000000000..9b4a5ffa30a78 --- /dev/null +++ b/eng/common/templates-official/steps/execute-codeql.yml @@ -0,0 +1,32 @@ +parameters: + # Language that should be analyzed. Defaults to csharp + language: csharp + # Build Commands + buildCommands: '' + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # Optional: if specified, restore and use this version of Guardian instead of the default. + overrideGuardianVersion: '' + # Optional: if true, publish the '.gdn' folder as a pipeline artifact. This can help with in-depth + # diagnosis of problems with specific tool configurations. + publishGuardianDirectoryToPipeline: false + # The script to run to execute all SDL tools. Use this if you want to use a script to define SDL + # parameters rather than relying on YAML. It may be better to use a local script, because you can + # reproduce results locally without piecing together a command based on the YAML. + executeAllSdlToolsScript: 'eng/common/sdl/execute-all-sdl-tools.ps1' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + # optional: determines whether to continue the build if the step errors; + sdlContinueOnError: false + +steps: +- template: /eng/common/templates-official/steps/execute-sdl.yml + parameters: + overrideGuardianVersion: ${{ parameters.overrideGuardianVersion }} + executeAllSdlToolsScript: ${{ parameters.executeAllSdlToolsScript }} + overrideParameters: ${{ parameters.overrideParameters }} + additionalParameters: '${{ parameters.additionalParameters }} + -CodeQLAdditionalRunConfigParams @("BuildCommands < ${{ parameters.buildCommands }}", "Language < ${{ parameters.language }}")' + publishGuardianDirectoryToPipeline: ${{ parameters.publishGuardianDirectoryToPipeline }} + sdlContinueOnError: ${{ parameters.sdlContinueOnError }} \ No newline at end of file diff --git a/eng/common/templates-official/steps/execute-sdl.yml b/eng/common/templates-official/steps/execute-sdl.yml new file mode 100644 index 0000000000000..07426fde05d82 --- /dev/null +++ b/eng/common/templates-official/steps/execute-sdl.yml @@ -0,0 +1,88 @@ +parameters: + overrideGuardianVersion: '' + executeAllSdlToolsScript: '' + overrideParameters: '' + additionalParameters: '' + publishGuardianDirectoryToPipeline: false + sdlContinueOnError: false + condition: '' + +steps: +- task: NuGetAuthenticate@1 + inputs: + nuGetServiceConnections: GuardianConnect + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + +- ${{ if ne(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts -Version ${{ parameters.overrideGuardianVersion }} + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian (Overridden) + +- ${{ if eq(parameters.overrideGuardianVersion, '') }}: + - pwsh: | + Set-Location -Path $(Build.SourcesDirectory)\eng\common\sdl + . .\sdl.ps1 + $guardianCliLocation = Install-Gdn -Path $(Build.SourcesDirectory)\.artifacts + Write-Host "##vso[task.setvariable variable=GuardianCliLocation]$guardianCliLocation" + displayName: Install Guardian + +- ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} ${{ parameters.overrideParameters }} + displayName: Execute SDL (Overridden) + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: ${{ parameters.executeAllSdlToolsScript }} + -GuardianCliLocation $(GuardianCliLocation) + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + condition: ${{ parameters.condition }} + +- ${{ if ne(parameters.publishGuardianDirectoryToPipeline, 'false') }}: + # We want to publish the Guardian results and configuration for easy diagnosis. However, the + # '.gdn' dir is a mix of configuration, results, extracted dependencies, and Guardian default + # tooling files. Some of these files are large and aren't useful during an investigation, so + # exclude them by simply deleting them before publishing. (As of writing, there is no documented + # way to selectively exclude a dir from the pipeline artifact publish task.) + - task: DeleteFiles@1 + displayName: Delete Guardian dependencies to avoid uploading + inputs: + SourceFolder: $(Agent.BuildDirectory)/.gdn + Contents: | + c + i + condition: succeededOrFailed() + + - publish: $(Agent.BuildDirectory)/.gdn + artifact: GuardianConfiguration + displayName: Publish GuardianConfiguration + condition: succeededOrFailed() + + # Publish the SARIF files in a container named CodeAnalysisLogs to enable integration + # with the "SARIF SAST Scans Tab" Azure DevOps extension + - task: CopyFiles@2 + displayName: Copy SARIF files + inputs: + flattenFolders: true + sourceFolder: $(Agent.BuildDirectory)/.gdn/rc/ + contents: '**/*.sarif' + targetFolder: $(Build.SourcesDirectory)/CodeAnalysisLogs + condition: succeededOrFailed() + + # Use PublishBuildArtifacts because the SARIF extension only checks this case + # see microsoft/sarif-azuredevops-extension#4 + - task: PublishBuildArtifacts@1 + displayName: Publish SARIF files to CodeAnalysisLogs container + inputs: + pathToPublish: $(Build.SourcesDirectory)/CodeAnalysisLogs + artifactName: CodeAnalysisLogs + condition: succeededOrFailed() \ No newline at end of file diff --git a/eng/common/templates-official/steps/generate-sbom.yml b/eng/common/templates-official/steps/generate-sbom.yml new file mode 100644 index 0000000000000..1bf43bf807af3 --- /dev/null +++ b/eng/common/templates-official/steps/generate-sbom.yml @@ -0,0 +1,48 @@ +# BuildDropPath - The root folder of the drop directory for which the manifest file will be generated. +# PackageName - The name of the package this SBOM represents. +# PackageVersion - The version of the package this SBOM represents. +# ManifestDirPath - The path of the directory where the generated manifest files will be placed +# IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. + +parameters: + PackageVersion: 8.0.0 + BuildDropPath: '$(Build.SourcesDirectory)/artifacts' + PackageName: '.NET' + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom + IgnoreDirectories: '' + sbomContinueOnError: true + +steps: +- task: PowerShell@2 + displayName: Prep for SBOM generation in (Non-linux) + condition: or(eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['Agent.Os'], 'Darwin')) + inputs: + filePath: ./eng/common/generate-sbom-prep.ps1 + arguments: ${{parameters.manifestDirPath}} + +# Chmodding is a workaround for https://github.com/dotnet/arcade/issues/8461 +- script: | + chmod +x ./eng/common/generate-sbom-prep.sh + ./eng/common/generate-sbom-prep.sh ${{parameters.manifestDirPath}} + displayName: Prep for SBOM generation in (Linux) + condition: eq(variables['Agent.Os'], 'Linux') + continueOnError: ${{ parameters.sbomContinueOnError }} + +- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'Generate SBOM manifest' + continueOnError: ${{ parameters.sbomContinueOnError }} + inputs: + PackageName: ${{ parameters.packageName }} + BuildDropPath: ${{ parameters.buildDropPath }} + PackageVersion: ${{ parameters.packageVersion }} + ManifestDirPath: ${{ parameters.manifestDirPath }} + ${{ if ne(parameters.IgnoreDirectories, '') }}: + AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' + +- task: 1ES.PublishPipelineArtifact@1 + displayName: Publish SBOM manifest + continueOnError: ${{parameters.sbomContinueOnError}} + inputs: + targetPath: '${{parameters.manifestDirPath}}' + artifactName: $(ARTIFACT_NAME) + diff --git a/eng/common/templates-official/steps/publish-logs.yml b/eng/common/templates-official/steps/publish-logs.yml new file mode 100644 index 0000000000000..04012fed182a1 --- /dev/null +++ b/eng/common/templates-official/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: 1ES.PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates-official/steps/retain-build.yml b/eng/common/templates-official/steps/retain-build.yml new file mode 100644 index 0000000000000..83d97a26a01ff --- /dev/null +++ b/eng/common/templates-official/steps/retain-build.yml @@ -0,0 +1,28 @@ +parameters: + # Optional azure devops PAT with build execute permissions for the build's organization, + # only needed if the build that should be retained ran on a different organization than + # the pipeline where this template is executing from + Token: '' + # Optional BuildId to retain, defaults to the current running build + BuildId: '' + # Azure devops Organization URI for the build in the https://dev.azure.com/ format. + # Defaults to the organization the current pipeline is running on + AzdoOrgUri: '$(System.CollectionUri)' + # Azure devops project for the build. Defaults to the project the current pipeline is running on + AzdoProject: '$(System.TeamProject)' + +steps: + - task: powershell@2 + inputs: + targetType: 'filePath' + filePath: eng/common/retain-build.ps1 + pwsh: true + arguments: > + -AzdoOrgUri: ${{parameters.AzdoOrgUri}} + -AzdoProject ${{parameters.AzdoProject}} + -Token ${{coalesce(parameters.Token, '$env:SYSTEM_ACCESSTOKEN') }} + -BuildId ${{coalesce(parameters.BuildId, '$env:BUILD_ID')}} + displayName: Enable permanent build retention + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + BUILD_ID: $(Build.BuildId) \ No newline at end of file diff --git a/eng/common/templates-official/steps/send-to-helix.yml b/eng/common/templates-official/steps/send-to-helix.yml new file mode 100644 index 0000000000000..3eb7e2d5f840c --- /dev/null +++ b/eng/common/templates-official/steps/send-to-helix.yml @@ -0,0 +1,91 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon-delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon-delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net ) + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates-official/steps/source-build.yml b/eng/common/templates-official/steps/source-build.yml new file mode 100644 index 0000000000000..829f17c34d118 --- /dev/null +++ b/eng/common/templates-official/steps/source-build.yml @@ -0,0 +1,129 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates-official/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- script: | + set -x + df -h + + # If building on the internal project, the artifact feeds variable may be available (usually only if needed) + # In that case, call the feed setup script to add internal feeds corresponding to public ones. + # In addition, add an msbuild argument to copy the WIP from the repo to the target build location. + # This is because SetupNuGetSources.sh will alter the current NuGet.config file, and we need to preserve those + # changes. + internalRestoreArgs= + if [ '$(dn-bot-dnceng-artifact-feeds-rw)' != '$''(dn-bot-dnceng-artifact-feeds-rw)' ]; then + # Temporarily work around https://github.com/dotnet/arcade/issues/7709 + chmod +x $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh + $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh $(Build.SourcesDirectory)/NuGet.config $(dn-bot-dnceng-artifact-feeds-rw) + internalRestoreArgs='/p:CopyWipIntoInnerSourceBuildRepo=true' + + # The 'Copy WIP' feature of source build uses git stash to apply changes from the original repo. + # This only works if there is a username/email configured, which won't be the case in most CI runs. + git config --get user.email + if [ $? -ne 0 ]; then + git config user.email dn-bot@microsoft.com + git config user.name dn-bot + fi + fi + + # If building on the internal project, the internal storage variable may be available (usually only if needed) + # In that case, add variables to allow the download of internal runtimes if the specified versions are not found + # in the default public locations. + internalRuntimeDownloadArgs= + if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + fi + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + runtimeOsArgs= + if [ '${{ parameters.platform.runtimeOS }}' != '' ]; then + runtimeOsArgs='/p:RuntimeOS=${{ parameters.platform.runtimeOS }}' + fi + + baseOsArgs= + if [ '${{ parameters.platform.baseOS }}' != '' ]; then + baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + assetManifestFileName=SourceBuild_RidSpecific.xml + if [ '${{ parameters.platform.name }}' != '' ]; then + assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $internalRuntimeDownloadArgs \ + $internalRestoreArgs \ + $targetRidArgs \ + $runtimeOsArgs \ + $baseOsArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true \ + /p:AssetManifestFileName=$assetManifestFileName + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: 1ES.PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() + +# Manually inject component detection so that we can ignore the source build upstream cache, which contains +# a nupkg cache of input packages (a local feed). +# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir' +# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets +- task: ComponentGovernanceComponentDetection@0 + displayName: Component Detection (Exclude upstream cache) + inputs: + ignoreDirectories: '$(Build.SourcesDirectory)/artifacts/source-build/self/src/artifacts/obj/source-built-upstream-cache' diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml new file mode 100644 index 0000000000000..1f308b24efc43 --- /dev/null +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -0,0 +1,45 @@ +# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, +# otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. + +# Motivation: +# Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS +# (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing +# (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. +# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services +# team needs to move resources around and create new and potentially differently-named pools. Using this template +# file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. + +# How to use: +# This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). +# If we find alternate naming conventions in broad usage it can be added to the condition below. +# +# First, import the template in an arcade-ified repo to pick up the variables, e.g.: +# +# variables: +# - template: /eng/common/templates-official/variables/pool-providers.yml +# +# ... then anywhere specifying the pool provider use the runtime variables, +# $(DncEngInternalBuildPool) +# +# pool: +# name: $(DncEngInternalBuildPool) +# image: 1es-windows-2022 + +variables: + # Coalesce the target and source branches so we know when a PR targets a release branch + # If these variables are somehow missing, fall back to main (tends to have more capacity) + + # Any new -Svc alternative pools should have variables added here to allow for splitting work + + - name: DncEngInternalBuildPool + value: $[ + replace( + replace( + eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), + True, + 'NetCore1ESPool-Svc-Internal' + ), + False, + 'NetCore1ESPool-Internal' + ) + ] \ No newline at end of file diff --git a/eng/common/templates-official/variables/sdl-variables.yml b/eng/common/templates-official/variables/sdl-variables.yml new file mode 100644 index 0000000000000..dbdd66d4a4b3a --- /dev/null +++ b/eng/common/templates-official/variables/sdl-variables.yml @@ -0,0 +1,7 @@ +variables: +# The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in +# sync with the packages.config file. +- name: DefaultGuardianVersion + value: 0.109.0 +- name: GuardianPackagesConfigFile + value: $(Build.SourcesDirectory)\eng\common\sdl\packages.config \ No newline at end of file diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index e20ee3a983cb0..8ec5c4f2d9f91 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -15,6 +15,7 @@ parameters: timeoutInMinutes: '' variables: [] workspace: '' + templateContext: '' # Job base template specific parameters # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md @@ -68,6 +69,9 @@ jobs: ${{ if ne(parameters.timeoutInMinutes, '') }}: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + ${{ if ne(parameters.templateContext, '') }}: + templateContext: ${{ parameters.templateContext }} + variables: - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE @@ -136,7 +140,7 @@ jobs: condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) - ${{ if and(eq(parameters.runAsPublic, 'false'), eq(variables['System.TeamProject'], 'internal')) }}: - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 - ${{ if and(ne(parameters.artifacts.download, 'false'), ne(parameters.artifacts.download, '')) }}: - task: DownloadPipelineArtifact@2 diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index 42017109f374d..8ec0151def21a 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -58,7 +58,7 @@ jobs: demands: Cmd # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: $(DncEngInternalBuildPool) + name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2019.amd64 steps: @@ -71,8 +71,8 @@ jobs: checkDownloadedFiles: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - - - task: NuGetAuthenticate@0 + + - task: NuGetAuthenticate@1 - task: PowerShell@2 displayName: Publish Build Assets @@ -81,12 +81,12 @@ jobs: arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' /p:BuildAssetRegistryToken=$(MaestroAccessToken) - /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:MaestroApiEndpoint=https://maestro.dot.net /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} /p:OfficialBuildId=$(Build.BuildNumber) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: powershell@2 displayName: Create ReleaseConfigs Artifact inputs: @@ -95,7 +95,7 @@ jobs: 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) - + - task: PublishBuildArtifacts@1 displayName: Publish ReleaseConfigs Artifact inputs: @@ -121,7 +121,7 @@ jobs: - task: PublishBuildArtifacts@1 displayName: Publish SymbolPublishingExclusionsFile Artifact - condition: eq(variables['SymbolExclusionFile'], 'true') + condition: eq(variables['SymbolExclusionFile'], 'true') inputs: PathtoPublish: '$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' PublishLocation: Container @@ -137,7 +137,7 @@ jobs: displayName: Publish Using Darc inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 - arguments: -BuildId $(BARBuildId) + arguments: -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' -MaestroToken '$(MaestroApiAccessToken)' @@ -148,4 +148,4 @@ jobs: - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - template: /eng/common/templates/steps/publish-logs.yml parameters: - JobLabel: 'Publish_Artifacts_Logs' + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml index c24193acfc981..173914f2364a7 100644 --- a/eng/common/templates/post-build/common-variables.yml +++ b/eng/common/templates/post-build/common-variables.yml @@ -7,7 +7,7 @@ variables: # Default Maestro++ API Endpoint and API Version - name: MaestroApiEndPoint - value: "https://maestro-prod.westus2.cloudapp.azure.com" + value: "https://maestro.dot.net" - name: MaestroApiAccessToken value: $(MaestroAccessToken) - name: MaestroApiVersion diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index ef720f9d78198..aba44a25a3387 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -39,7 +39,7 @@ parameters: displayName: Enable NuGet validation type: boolean default: true - + - name: publishInstallersAndChecksums displayName: Publish installers and checksums type: boolean @@ -131,8 +131,8 @@ stages: displayName: Validate inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 - arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ - job: displayName: Signing Validation @@ -169,7 +169,7 @@ stages: # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here # otherwise it'll complain about accessing a private feed. - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 displayName: 'Authenticate to AzDO Feeds' # Signing validation will optionally work with the buildmanifest file which is downloaded from @@ -221,9 +221,9 @@ stages: displayName: Validate inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 - arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ - -ExtractPath $(Agent.BuildDirectory)/Extract/ - -GHRepoName $(Build.Repository.Name) + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) -GHCommit $(Build.SourceVersion) -SourcelinkCliVersion $(SourceLinkCLIVersion) continueOnError: true @@ -258,7 +258,7 @@ stages: demands: Cmd # If it's not devdiv, it's dnceng ${{ else }}: - name: $(DncEngInternalBuildPool) + name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2019.amd64 steps: - template: setup-maestro-vars.yml @@ -266,13 +266,13 @@ stages: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 - task: PowerShell@2 displayName: Publish Using Darc inputs: filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 - arguments: -BuildId $(BARBuildId) + arguments: -BuildId $(BARBuildId) -PublishingInfraVersion ${{ parameters.publishingInfraVersion }} -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' -MaestroToken '$(MaestroApiAccessToken)' 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/common/templates/steps/generate-sbom.yml b/eng/common/templates/steps/generate-sbom.yml index a06373f38fa5d..2b21eae427328 100644 --- a/eng/common/templates/steps/generate-sbom.yml +++ b/eng/common/templates/steps/generate-sbom.yml @@ -5,7 +5,7 @@ # IgnoreDirectories - Directories to ignore for SBOM generation. This will be passed through to the CG component detector. parameters: - PackageVersion: 7.0.0 + PackageVersion: 8.0.0 BuildDropPath: '$(Build.SourcesDirectory)/artifacts' PackageName: '.NET' ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom diff --git a/eng/common/templates/variables/pool-providers.yml b/eng/common/templates/variables/pool-providers.yml index 9cc5c550d3b36..d236f9fdbb153 100644 --- a/eng/common/templates/variables/pool-providers.yml +++ b/eng/common/templates/variables/pool-providers.yml @@ -1,15 +1,15 @@ -# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, +# Select a pool provider based off branch name. Anything with branch name containing 'release' must go into an -Svc pool, # otherwise it should go into the "normal" pools. This separates out the queueing and billing of released branches. -# Motivation: +# Motivation: # Once a given branch of a repository's output has been officially "shipped" once, it is then considered to be COGS # (Cost of goods sold) and should be moved to a servicing pool provider. This allows both separation of queueing # (allowing release builds and main PR builds to not intefere with each other) and billing (required for COGS. -# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services -# team needs to move resources around and create new and potentially differently-named pools. Using this template +# Additionally, the pool provider name itself may be subject to change when the .NET Core Engineering Services +# team needs to move resources around and create new and potentially differently-named pools. Using this template # file from an Arcade-ified repo helps guard against both having to update one's release/* branches and renaming. -# How to use: +# How to use: # This yaml assumes your shipped product branches use the naming convention "release/..." (which many do). # If we find alternate naming conventions in broad usage it can be added to the condition below. # @@ -54,4 +54,4 @@ variables: False, 'NetCore1ESPool-Internal' ) - ] \ No newline at end of file + ] diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index aa74ab4a81e78..eb188cfda415b 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -379,13 +379,13 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = } # Minimum VS version to require. - $vsMinVersionReqdStr = '17.6' + $vsMinVersionReqdStr = '17.7' $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.6.0-2 - $defaultXCopyMSBuildVersion = '17.6.0-2' + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.8.1-2 + $defaultXCopyMSBuildVersion = '17.8.1-2' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { @@ -601,7 +601,15 @@ function InitializeBuildTool() { ExitWithExitCode 1 } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') - $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net8.0' } + + # Use override if it exists - commonly set by source-build + if ($null -eq $env:_OverrideArcadeInitializeBuildToolFramework) { + $initializeBuildToolFramework="net8.0" + } else { + $initializeBuildToolFramework=$env:_OverrideArcadeInitializeBuildToolFramework + } + + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = $initializeBuildToolFramework } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore diff --git a/eng/common/tools.sh b/eng/common/tools.sh index e8d478943341d..3392e3a99921f 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -341,7 +341,12 @@ function InitializeBuildTool { # return values _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" - _InitializeBuildToolFramework="net8.0" + # use override if it exists - commonly set by source-build + if [[ "${_OverrideArcadeInitializeBuildToolFramework:-x}" == "x" ]]; then + _InitializeBuildToolFramework="net8.0" + else + _InitializeBuildToolFramework="${_OverrideArcadeInitializeBuildToolFramework}" + fi } # Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116 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/global.json b/global.json index 3cb7adc0a9eb6..d08e9d50a69a2 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.6.0-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.23461.2", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23461.2" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24204.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24204.3" } } From 9030db36c6d8353248b96b97a234d7ec22c6d11e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 09:04:21 -0700 Subject: [PATCH 0820/1047] Avoid unnecessary boxing --- .../AbstractWorkspaceDocumentDiagnosticSource.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index cc301aa60d8b3..4bff28c7035c8 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; @@ -31,7 +29,7 @@ private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we /// walk every document within it. /// - private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. @@ -67,13 +65,13 @@ private async ValueTask> GetProjectDiagnosticsAsy } var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); - return (ImmutableArray)result; + return result; - AsyncLazy> GetLazyDiagnostics() + AsyncLazy> GetLazyDiagnostics() { return s_projectToDiagnostics.GetValue( Document.Project, - _ => AsyncLazy.Create>( + _ => AsyncLazy.Create( async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer, From 540104364d182a5e4e8b4373cb07f5af9f7657a9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 09:29:54 -0700 Subject: [PATCH 0821/1047] Also switch to a lookup --- ...stractWorkspaceDocumentDiagnosticSource.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4bff28c7035c8..c3b1a48eac2e0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -29,7 +29,7 @@ private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we /// walk every document within it. /// - private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. @@ -65,20 +65,24 @@ private async ValueTask> GetProjectDiagnosticsAsy } var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); - return result; + return result[Document.Id].ToImmutableArray(); - AsyncLazy> GetLazyDiagnostics() + AsyncLazy> GetLazyDiagnostics() { return s_projectToDiagnostics.GetValue( Document.Project, _ => AsyncLazy.Create( - async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, - // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. - static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], - includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); + async cancellationToken => + { + var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + return allDiagnostics.Where(d => d.DocumentId != null).ToLookup(d => d.DocumentId!); + })); } } } From 712fa16e06aca9a73e50c1c2bc486ee099cacbde Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 09:30:39 -0700 Subject: [PATCH 0822/1047] Make hot reload diagnostics more pass thru --- .../DiagnosticSourceManager.cs | 41 ++++++++------ .../Contracts/HotReloadDocumentDiagnostics.cs | 14 ----- .../Contracts/HotReloadRequestContext.cs | 17 ++++++ .../Contracts/IHotReloadDiagnosticManager.cs | 40 ++++++------- .../Contracts/IHotReloadDiagnosticSource.cs | 25 ++++----- .../IHotReloadDiagnosticSourceProvider.cs | 27 +++++++++ ...stractHotReloadDiagnosticSourceProvider.cs | 21 ------- ...cumentHotReloadDiagnosticSourceProvider.cs | 41 -------------- .../Internal/HotReloadDiagnosticManager.cs | 29 ++++++---- .../Internal/HotReloadDiagnosticSource.cs | 13 +++-- .../HotReloadDiagnosticSourceProvider.cs | 56 +++++++++++++++++++ ...kspaceHotReloadDiagnosticSourceProvider.cs | 51 ----------------- .../InternalAPI.Unshipped.txt | 45 ++++++++------- 13 files changed, 205 insertions(+), 215 deletions(-) delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index dffea4db7b4ae..f213c0b3e0495 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -71,27 +71,32 @@ public async ValueTask> CreateDiagnosticSource } var sources = sourcesBuilder.ToImmutableAndClear(); - if (sources.Length <= 1) - { - return sources; - } - - if (isDocument) - { - // Group all document sources into a single source. - Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); - sources = [new AggregatedDocumentDiagnosticSource(sources)]; - } - else - { - // For workspace we need to group sources by source id and IsLiveSource - sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) - .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) - .ToImmutableArray(); - } + return AggregateSourcesIfNeeded(sources, isDocument); + } + } + public static ImmutableArray AggregateSourcesIfNeeded(ImmutableArray sources, bool isDocument) + { + if (sources.Length <= 1) + { return sources; } + + if (isDocument) + { + // Group all document sources into a single source. + Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); + sources = [new AggregatedDocumentDiagnosticSource(sources)]; + } + else + { + // For workspace we need to group sources by source id and IsLiveSource + sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) + .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) + .ToImmutableArray(); + } + + return sources; } private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs deleted file mode 100644 index 13e899beb1c4c..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts -{ - internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray diagnostics) - { - public DocumentId DocumentId => documentId; - public ImmutableArray Diagnostics => diagnostics; - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs new file mode 100644 index 0000000000000..6e879f2535158 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal class HotReloadRequestContext(RequestContext context) +{ + internal LSP.ClientCapabilities ClientCapabilities => context.GetRequiredClientCapabilities(); + public TextDocument? TextDocument => context.TextDocument; + public Solution? Solution => context.Solution; + public bool IsTracking(TextDocument textDocument) => context.IsTracking(textDocument.GetURI()); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs index 29d9562916cdc..f1fe7a600dae4 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -2,30 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Collections.Immutable; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal interface IHotReloadDiagnosticManager { - internal interface IHotReloadDiagnosticManager - { - /// - /// Hot reload diagnostics for all sources. - /// - ImmutableArray Sources { get; } + /// + /// Refreshes hot reload diagnostics. + /// + void RequestRefresh(); - /// - /// Registers source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. - /// - void Register(IHotReloadDiagnosticSource source); + /// + /// Registers providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. + /// + void Register(IEnumerable providers); - /// - /// Unregisters source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. - /// - void Unregister(IHotReloadDiagnosticSource source); + /// + /// Unregisters providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. + /// + void Unregister(IEnumerable providers); - /// - /// Requests refresh of hot reload diagnostics. - /// - void Refresh(); - } + /// + /// Providers. + /// + ImmutableArray Providers { get; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs index 661ecc1f05724..05f1cee575b41 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -6,21 +6,20 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Source of hot reload diagnostics. +/// +internal interface IHotReloadDiagnosticSource { /// - /// Source for hot reload diagnostics. + /// Text document for which diagnostics are provided. /// - internal interface IHotReloadDiagnosticSource - { - /// - /// Provides list of document ids that have hot reload diagnostics. - /// - ValueTask> GetDocumentIdsAsync(CancellationToken cancellationToken); + TextDocument TextDocument { get; } - /// - /// Provides list of diagnostics for the given document. - /// - ValueTask> GetDocumentDiagnosticsAsync(TextDocument document, CancellationToken cancellationToken); - } + /// + /// Provides list of diagnostics for the given document. + /// + ValueTask> GetDiagnosticsAsync(HotReloadRequestContext request, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..586a6a2d8ec90 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Provides diagnostic sources. +/// +internal interface IHotReloadDiagnosticSourceProvider +{ + /// + /// True if this provider is for documents. False if it is for a workspace, i.e. for unopened documents. + /// + bool IsDocument { get; } + + /// + /// Creates the diagnostic sources. + /// + /// The context. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(HotReloadRequestContext context, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index 832c1c6aa7c97..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -internal abstract class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider -{ - string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; - bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - => throw new NotImplementedException(); - -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index f568fffbc8a43..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -[Export(typeof(IDiagnosticSourceProvider)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadDiagnosticManager) - : AbstractHotReloadDiagnosticSourceProvider - , IDiagnosticSourceProvider -{ - bool IDiagnosticSourceProvider.IsDocument => true; - - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.GetTrackedDocument() is { } textDocument) - { - List sources = new(); - foreach (var hotReloadSource in hotReloadDiagnosticManager.Sources) - { - sources.Add(new HotReloadDiagnosticSource(textDocument, hotReloadSource)); - } - - return new(sources.ToImmutableArray()); - } - - return new([]); - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index b045e419d3eb7..8ce6103c47a01 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; @@ -14,20 +15,26 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [Export(typeof(IHotReloadDiagnosticManager)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class HotReloadDiagnosticManager(IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager +internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { - private ImmutableArray _sources = ImmutableArray.Empty; + private ImmutableArray _providers = ImmutableArray.Empty; + ImmutableArray IHotReloadDiagnosticManager.Providers => _providers; + void IHotReloadDiagnosticManager.RequestRefresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); - ImmutableArray IHotReloadDiagnosticManager.Sources => _sources; - void IHotReloadDiagnosticManager.Refresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); - - void IHotReloadDiagnosticManager.Register(IHotReloadDiagnosticSource source) + void IHotReloadDiagnosticManager.Register(IEnumerable providers) { - // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. - if (!_sources.Contains(source)) - _sources = _sources.Add(source); + // We use array instead of e.g. HashSet because we expect the number of sources to be small. + // Usually 2, one workspace and one document provider. + foreach (var provider in providers) + { + if (!_providers.Contains(provider)) + _providers = _providers.Add(provider); + } } - void IHotReloadDiagnosticManager.Unregister(IHotReloadDiagnosticSource source) - => _sources = _sources.Remove(source); + void IHotReloadDiagnosticManager.Unregister(IEnumerable providers) + { + foreach (var provider in providers) + _providers = _providers.Remove(provider); + } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index 5d41c5a98860e..101865e806152 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -15,19 +15,20 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal { - internal class HotReloadDiagnosticSource(TextDocument document, IHotReloadDiagnosticSource hotReloadDiagnosticSource) : IDiagnosticSource + internal class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source) : IDiagnosticSource { async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { - var diagnostics = await hotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); + var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false); + var document = source.TextDocument; var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); return result; } - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; - ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); - Project IDiagnosticSource.GetProject() => document.Project; + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = source.TextDocument.GetURI() }; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(source.TextDocument.Id); + Project IDiagnosticSource.GetProject() => source.TextDocument.Project; bool IDiagnosticSource.IsLiveSource() => true; - string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {source.TextDocument.FilePath ?? source.TextDocument.Name} in {source.TextDocument.Project.Name}"; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..97c7f3f663561 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager diagnosticManager, bool isDocument) : IDiagnosticSourceProvider +{ + string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; + bool IDiagnosticSourceProvider.IsDocument => isDocument; + + async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + var hotReloadContext = new HotReloadRequestContext(context); + List sources = new(); + foreach (var provider in diagnosticManager.Providers) + { + if (provider.IsDocument == isDocument) + { + var hotReloadSources = await provider.CreateDiagnosticSourcesAsync(hotReloadContext, cancellationToken).ConfigureAwait(false); + sources.AddRange(hotReloadSources.Select(s => new HotReloadDiagnosticSource(s))); + } + } + + var result = sources.ToImmutableArray(); + return DiagnosticSourceManager.AggregateSourcesIfNeeded(result, isDocument); + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class DocumentHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: true) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class WorkspaceHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: false) + { + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index c147997aac46d..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -[Export(typeof(IDiagnosticSourceProvider)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) - : AbstractHotReloadDiagnosticSourceProvider - , IDiagnosticSourceProvider -{ - bool IDiagnosticSourceProvider.IsDocument => false; - - async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.Solution is not Solution solution) - { - return []; - } - - using var _ = ArrayBuilder.GetInstance(out var builder); - foreach (var hotReloadSource in hotReloadErrorService.Sources) - { - var docIds = await hotReloadSource.GetDocumentIdsAsync(cancellationToken).ConfigureAwait(false); - foreach (var docId in docIds) - { - if (solution.GetDocument(docId) is { } document && !context.IsTracking(document.GetURI())) - { - builder.Add(new HotReloadDiagnosticSource(document, hotReloadSource)); - } - } - } - - var result = builder.ToImmutableAndClear(); - return result; - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 368b69575bb21..70c2918b8f69c 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,35 +1,40 @@ -const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceName = "HotReloadDiagnostic" -> string! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Diagnostics.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray diagnostics) -> void +abstract Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.ClientCapabilities.get -> Roslyn.LanguageServer.Protocol.ClientCapabilities! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.HotReloadRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.IsTracking(Microsoft.CodeAnalysis.TextDocument! textDocument) -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.Solution.get -> Microsoft.CodeAnalysis.Solution? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument? Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Refresh() -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Sources.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(Microsoft.CodeAnalysis.TextDocument! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentIdsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.IsDocument.get -> bool Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.AbstractHotReloadDiagnosticSourceProvider() -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadDiagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! hotReloadDiagnosticSource) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager, bool isDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.CreateILspService(Microsoft.CodeAnalysis.LanguageServer.LspServices! lspServices, Microsoft.CodeAnalysis.LanguageServer.WellKnownLspServerKinds serverKind) -> Microsoft.CodeAnalysis.LanguageServer.ILspService! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void -static readonly Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceNames -> System.Collections.Immutable.ImmutableArray \ No newline at end of file +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void \ No newline at end of file From 155cf4a4a40e975d28b612e5c7135e6c588e9565 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 10:27:20 -0700 Subject: [PATCH 0823/1047] Rename types --- .../VisualStudioMetadataReference.Snapshot.cs | 2 +- .../VisualStudioMetadataReferenceManager.cs | 12 ++++---- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 22 +++++++-------- ...ageService.TemporaryStorageStreamHandle.cs | 28 +++++++++++++++++++ .../TemporaryStorageService.cs | 22 ++------------- .../ITemporaryStorageService.cs | 2 +- ...le.cs => ITemporaryStorageStreamHandle.cs} | 2 +- ...orageService.TrivialStorageStreamHandle.cs | 22 +++++++++++++++ .../TrivialTemporaryStorageService.cs | 16 ++--------- .../ProjectSystemProjectOptionsProcessor.cs | 4 +-- ...CompilationState.SkeletonReferenceCache.cs | 2 +- ...onCompilationState.SkeletonReferenceSet.cs | 4 +-- .../TemporaryStorageServiceTests.cs | 4 +-- 14 files changed, 84 insertions(+), 60 deletions(-) create mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs rename src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/{ITemporaryStorageHandle.cs => ITemporaryStorageStreamHandle.cs} (95%) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 81388674db446..761a41ea3d178 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageHandles + public IReadOnlyList StorageHandles => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 6fa856f0c688f..01a6a41f6695d 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -45,7 +45,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the /// host to send the entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -88,7 +88,7 @@ public void Dispose() } } - public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata @@ -161,7 +161,7 @@ AssemblyMetadata GetMetadataWorker() } } - private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) GetMetadataFromTemporaryStorage( + private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) GetMetadataFromTemporaryStorage( FileKey moduleFileKey) { GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storageHandle, out var stream); @@ -176,7 +176,7 @@ AssemblyMetadata GetMetadataWorker() } void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageStreamHandle storageHandle, out UnmanagedMemoryStream stream) { int size; @@ -301,12 +301,12 @@ bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] /// private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, - Func moduleMetadataFactory) + Func moduleMetadataFactory) { var (manifestModule, manifestHandle) = moduleMetadataFactory(fileKey); using var _1 = ArrayBuilder.GetInstance(out var moduleBuilder); - using var _2 = ArrayBuilder.GetInstance(out var storageHandles); + using var _2 = ArrayBuilder.GetInstance(out var storageHandles); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index 84dc4dc0834bd..a7c02bba2bfc5 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageHandles { get; } + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 031ce659fd857..e958468cd5861 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -309,7 +309,7 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( PortableExecutableReference reference, - IReadOnlyList handles, + IReadOnlyList handles, ObjectWriter writer, CancellationToken cancellationToken) { @@ -329,7 +329,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return true; } - private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -345,7 +345,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var count = reader.ReadInt32(); var allMetadata = new FixedSizeArrayBuilder(count); - var allHandles = new FixedSizeArrayBuilder(count); + var allHandles = new FixedSizeArrayBuilder(count); for (var i = 0; i < count; i++) { @@ -369,7 +369,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } } - private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -380,7 +380,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT ? ReadModuleMetadataFromBits() : ReadModuleMetadataFromMemoryMappedFile(); - (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. @@ -389,7 +389,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromBits() + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromBits() { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the // server side so that we can refer to this data uniformly. @@ -402,8 +402,8 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromStorage( - TemporaryStorageHandle storageHandle) + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromStorage( + TemporaryStorageStreamHandle storageHandle) { // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they @@ -504,16 +504,16 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storageHandles; + private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; - public IReadOnlyList StorageHandles => _storageHandles; + public IReadOnlyList StorageHandles => _storageHandles; public SerializedMetadataReference( MetadataReferenceProperties properties, string? fullPath, Metadata metadata, - ImmutableArray storageHandles, + ImmutableArray storageHandles, DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs new file mode 100644 index 0000000000000..62762d0ee869b --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Threading; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + public sealed class TemporaryStorageStreamHandle( + TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageStreamHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + Stream ITemporaryStorageStreamHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) + => ReadFromTemporaryStorage(cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage( + storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); + return storage.ReadStream(cancellationToken); + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index 87c1bc80d50c9..b537a951e38bd 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -101,10 +101,10 @@ public TemporaryTextStorage AttachTemporaryTextStorage( string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - ITemporaryStorageHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) => WriteToTemporaryStorage(stream, cancellationToken); - public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new TemporaryStreamStorage(this); @@ -113,7 +113,7 @@ public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, Cancellatio return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); } - internal TemporaryStorageHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + internal TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) { var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); return new(this, memoryMappedFile, storageIdentifier); @@ -164,22 +164,6 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) public static string CreateUniqueName(long size) => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - public sealed class TemporaryStorageHandle( - TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageHandle - { - public TemporaryStorageIdentifier Identifier => identifier; - - Stream ITemporaryStorageHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) - => ReadFromTemporaryStorage(cancellationToken); - - public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage( - storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); - return storage.ReadStream(cancellationToken); - } - } - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 9b86919667133..b9d1f9d530efc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -44,7 +44,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// cref="Stream.Position"/> 0 within this method. The caller does not need to reset the stream /// itself. /// - ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs similarity index 95% rename from src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs rename to src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 3d2be4b29e531..e4689f2ba1381 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Host; /// to temporary storage and get a handle to it. Use to read the data back in /// any process. /// -internal interface ITemporaryStorageHandle +internal interface ITemporaryStorageStreamHandle { public TemporaryStorageIdentifier Identifier { get; } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs new file mode 100644 index 0000000000000..2b750b62a5807 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Threading; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis; + +internal sealed partial class TrivialTemporaryStorageService +{ + private sealed class TrivialStorageStreamHandle( + TemporaryStorageIdentifier storageIdentifier, + StreamStorage streamStorage) : ITemporaryStorageStreamHandle + { + public TemporaryStorageIdentifier Identifier => storageIdentifier; + + public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) + => streamStorage.ReadStream(); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 28f2b4ba4c66e..7b09b0d5bc9df 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis; -internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal +internal sealed partial class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal { public static readonly TrivialTemporaryStorageService Instance = new(); @@ -24,26 +24,16 @@ private TrivialTemporaryStorageService() public ITemporaryTextStorageInternal CreateTemporaryTextStorage() => new TextStorage(); - public ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TrivialStorageHandle(identifier, storage); + var handle = new TrivialStorageStreamHandle(identifier, storage); return handle; } - private sealed class TrivialStorageHandle( - TemporaryStorageIdentifier storageIdentifier, - StreamStorage streamStorage) : ITemporaryStorageHandle - { - public TemporaryStorageIdentifier Identifier => storageIdentifier; - - public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) - => streamStorage.ReadStream(); - } - private sealed class StreamStorage { private MemoryStream? _stream; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 950cbab4a7b8a..9c2059d1006c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -38,7 +38,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private ITemporaryStorageHandle? _commandLineStorageHandle; + private ITemporaryStorageStreamHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -254,7 +254,7 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) } static IEnumerable EnumerateLines( - ITemporaryStorageHandle storageHandle) + ITemporaryStorageStreamHandle storageHandle) { using var stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); using var reader = new StreamReader(stream); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 0735a5f50c9cf..2fbf3bc719d90 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -230,7 +230,7 @@ public readonly SkeletonReferenceCache Clone() compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - (AssemblyMetadata? metadata, ITemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() + (AssemblyMetadata? metadata, ITemporaryStorageStreamHandle storageHandle) TryCreateMetadataAndHandle() { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 9d579a586ddf3..e3e9b84b07744 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -19,7 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, - ITemporaryStorageHandle storageHandle, + ITemporaryStorageStreamHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -31,7 +31,7 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; - public ITemporaryStorageHandle StorageHandle => storageHandle; + public ITemporaryStorageStreamHandle StorageHandle => storageHandle; public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index b859c52a4c186..2bd1e0f417690 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -116,7 +116,7 @@ public void TestZeroLengthStreams() var service = Assert.IsType(workspace.Services.GetRequiredService()); // 0 length streams are allowed - TemporaryStorageHandle handle; + TemporaryStorageStreamHandle handle; using (var stream1 = new MemoryStream()) { handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); @@ -185,7 +185,7 @@ public void TestTemporaryStorageScaling() // Create 4GB of memory mapped files var fileCount = (int)((long)4 * 1024 * 1024 * 1024 / data.Length); - var storageHandles = new List(fileCount); + var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); From 0abbdda012b38d4e0245edb9d54e333b16544046 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 10:36:56 -0700 Subject: [PATCH 0824/1047] Add initial stubs and trivial impl --- .../TemporaryStorage/ITemporaryStorage.cs | 14 ++++---- .../ITemporaryStorageService.cs | 5 ++- .../ITemporaryStorageStreamHandle.cs | 16 +++++++-- ...StorageService.TrivialStorageTextHandle.cs | 26 ++++++++++++++ .../TrivialTemporaryStorageService.cs | 34 +++++++++---------- 5 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index aaac0a3ac1e6c..8e4dde76b2938 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -33,10 +33,10 @@ public interface ITemporaryStreamStorage : IDisposable /// /// TemporaryStorage can be used to read and write text to a temporary storage location. /// -internal interface ITemporaryTextStorageInternal -{ - SourceText ReadText(CancellationToken cancellationToken = default); - Task ReadTextAsync(CancellationToken cancellationToken = default); - void WriteText(SourceText text, CancellationToken cancellationToken = default); - Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); -} +//internal interface ITemporaryTextStorageInternal +//{ +// SourceText ReadText(CancellationToken cancellationToken = default); +// Task ReadTextAsync(CancellationToken cancellationToken = default); +// void WriteText(SourceText text, CancellationToken cancellationToken = default); +// Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); +//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index b9d1f9d530efc..c0c7b5c80f731 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Host; @@ -46,5 +48,6 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); - ITemporaryTextStorageInternal CreateTemporaryTextStorage(); + ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken); + Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index e4689f2ba1381..460241f660790 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -4,15 +4,17 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Host; /// /// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is /// alive, the data should remain in storage and can be readable from any process using the information provided in . Use to write the data -/// to temporary storage and get a handle to it. Use to read the data back in -/// any process. +/// cref="Identifier"/>. Use to write the data to temporary storage and get a handle to it. Use to read the data back in any process. /// internal interface ITemporaryStorageStreamHandle { @@ -24,3 +26,11 @@ internal interface ITemporaryStorageStreamHandle /// Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); } + +internal interface ITemporaryStorageTextHandle +{ + public TemporaryStorageIdentifier Identifier { get; } + + SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); + Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs new file mode 100644 index 0000000000000..9bef5f0738b8c --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis; + +internal sealed partial class TrivialTemporaryStorageService +{ + private sealed class TrivialStorageTextHandle( + TemporaryStorageIdentifier identifier, + TextStorage storage) : ITemporaryStorageTextHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) + => storage.ReadText(); + + public Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) + => Task.FromResult(ReadFromTemporaryStorage(cancellationToken)); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 7b09b0d5bc9df..113636d000d9d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -4,12 +4,10 @@ using System; using System.IO; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -21,8 +19,17 @@ private TrivialTemporaryStorageService() { } - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TextStorage(); + public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + var storage = new TextStorage(); + storage.WriteText(text); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); + var handle = new TrivialStorageTextHandle(identifier, storage); + return handle; + } + + public Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + => Task.FromResult(WriteToTemporaryStorage(text, cancellationToken)); public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { @@ -59,20 +66,17 @@ public void WriteStream(Stream stream) } } - private sealed class TextStorage : ITemporaryTextStorageInternal + private sealed class TextStorage { private SourceText? _sourceText; - public void Dispose() - => _sourceText = null; - - public SourceText ReadText(CancellationToken cancellationToken) + public SourceText ReadText() => _sourceText ?? throw new InvalidOperationException(); - public Task ReadTextAsync(CancellationToken cancellationToken) - => Task.FromResult(ReadText(cancellationToken)); + public Task ReadTextAsync() + => Task.FromResult(ReadText()); - public void WriteText(SourceText text, CancellationToken cancellationToken) + public void WriteText(SourceText text) { // This is a trivial implementation, indeed. Note, however, that we retain a strong // reference to the source text, which defeats the intent of RecoverableTextAndVersion, but @@ -81,11 +85,5 @@ public void WriteText(SourceText text, CancellationToken cancellationToken) if (existingValue is not null) throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); } - - public Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default) - { - WriteText(text, cancellationToken); - return Task.CompletedTask; - } } } From be63421fb3afcd97b8e8f366a7193cdf1fdbeb86 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 10:59:22 -0700 Subject: [PATCH 0825/1047] Flesh out --- ...eService.DirectMemoryAccessStreamReader.cs | 76 ++++++ ...ageService.TemporaryStorageStreamHandle.cs | 15 +- .../TemporaryStorageService.cs | 242 ++++++++---------- .../ITemporaryStorageStreamHandle.cs | 25 +- .../ITemporaryStorageWithName.cs | 40 +-- ...StorageService.TrivialStorageTextHandle.cs | 4 +- .../TrivialTemporaryStorageService.cs | 2 +- 7 files changed, 235 insertions(+), 169 deletions(-) create mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs new file mode 100644 index 0000000000000..9918414654ea1 --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + private unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength + { + private char* _position; + private readonly char* _end; + + public DirectMemoryAccessStreamReader(char* src, int length) + : base(length) + { + RoslynDebug.Assert(src != null); + RoslynDebug.Assert(length >= 0); + + _position = src; + _end = _position + length; + } + + public override int Peek() + { + if (_position >= _end) + { + return -1; + } + + return *_position; + } + + public override int Read() + { + if (_position >= _end) + { + return -1; + } + + return *_position++; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0 || index >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || (index + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + count = Math.Min(count, (int)(_end - _position)); + if (count > 0) + { + Marshal.Copy((IntPtr)_position, buffer, index, count); + _position += count; + } + + return count; + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs index 62762d0ee869b..7b06db02ddbff 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -5,13 +5,16 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Threading; +using Microsoft.CodeAnalysis.Internal.Log; namespace Microsoft.CodeAnalysis.Host; internal sealed partial class TemporaryStorageService { public sealed class TemporaryStorageStreamHandle( - TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageStreamHandle + MemoryMappedFile memoryMappedFile, + TemporaryStorageIdentifier identifier) + : ITemporaryStorageStreamHandle { public TemporaryStorageIdentifier Identifier => identifier; @@ -20,9 +23,13 @@ Stream ITemporaryStorageStreamHandle.ReadFromTemporaryStorage(CancellationToken public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) { - var storage = new TemporaryStreamStorage( - storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); - return storage.ReadStream(cancellationToken); + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + return info.CreateReadableStream(); + } } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index b537a951e38bd..c349cadcd41e4 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -94,12 +94,39 @@ private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingSe _textFactory = textFactory; } - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TemporaryTextStorage(this); + //public ITemporaryTextStorageInternal CreateTemporaryTextStorage() + // => new TemporaryTextStorage(this); - public TemporaryTextStorage AttachTemporaryTextStorage( - string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) - => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + //public TemporaryTextStorage AttachTemporaryTextStorage( + // string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) + // => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + + ITemporaryStorageTextHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + => WriteToTemporaryStorage(text, cancellationToken); + + async Task ITemporaryStorageServiceInternal.WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + => await WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); + + public TemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + var storage = new TemporaryTextStorage(this); + storage.WriteText(text, cancellationToken); + return CreateHandleFromStorage(storage); + } + + public async Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + { + var storage = new TemporaryTextStorage(this); + await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); + return CreateHandleFromStorage(storage); + } + + private TemporaryStorageTextHandle CreateHandleFromStorage(TemporaryTextStorage storage) + { + var identifier = new TemporaryStorageTextIdentifier( + storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding); + return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); + } ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) => WriteToTemporaryStorage(stream, cancellationToken); @@ -110,13 +137,13 @@ public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, Cance var storage = new TemporaryStreamStorage(this); storage.WriteStream(stream, cancellationToken); var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); + return new(storage.MemoryMappedInfo.MemoryMappedFile, identifier); } - internal TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + internal static TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) { var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); - return new(this, memoryMappedFile, storageIdentifier); + return new(memoryMappedFile, storageIdentifier); } /// @@ -164,7 +191,67 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) public static string CreateUniqueName(long size) => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName + public sealed class TemporaryStorageTextHandle( + TemporaryStorageService storageService, + MemoryMappedFile memoryMappedFile, + TemporaryStorageTextIdentifier identifier) + : ITemporaryStorageTextHandle + { + public TemporaryStorageTextIdentifier Identifier => identifier; + + public async Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) + { + // There is a reason for implementing it like this: proper async implementation + // that reads the underlying memory mapped file stream in an asynchronous fashion + // doesn't actually work. Windows doesn't offer + // any non-blocking way to read from a memory mapped file; the underlying memcpy + // may block as the memory pages back in and that's something you have to live + // with. Therefore, any implementation that attempts to use async will still + // always be blocking at least one threadpool thread in the memcpy in the case + // of a page fault. Therefore, if we're going to be blocking a thread, we should + // just block one thread and do the whole thing at once vs. a fake "async" + // implementation which will continue to requeue work back to the thread pool. + if (storageService._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return ReadFromTemporaryStorage(cancellationToken); + } + + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) + { + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + using var stream = info.CreateReadableStream(); + using var reader = CreateTextReaderFromTemporaryStorage(stream); + + // we pass in encoding we got from original source text even if it is null. + return storageService._textFactory.CreateText(reader, Identifier.Encoding, Identifier.ChecksumAlgorithm, cancellationToken); + } + } + + private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + { + var src = (char*)stream.PositionPointer; + + // BOM: Unicode, little endian + // Skip the BOM when creating the reader + Debug.Assert(*src == 0xFEFF); + + return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); + } + +#if false + var storage = new TemporaryStreamStorage( + storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); + return storage.ReadStream(cancellationToken); +#endif + } + + public sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; private SourceHashAlgorithm _checksumAlgorithm; @@ -172,6 +259,8 @@ public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITempo private ImmutableArray _contentHash; private MemoryMappedInfo? _memoryMappedInfo; + public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); + public TemporaryTextStorage(TemporaryStorageService service) => _service = service; @@ -191,11 +280,9 @@ public TemporaryTextStorage( _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); } - // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size not accessed if Name is null - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; + public string Name => this.MemoryMappedInfo.Name; + public long Offset => this.MemoryMappedInfo.Offset; + public long Size => this.MemoryMappedInfo.Size; /// /// Gets the value for the property for the @@ -215,44 +302,6 @@ public TemporaryTextStorage( /// public ImmutableArray ContentHash => _contentHash; - public SourceText ReadText(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) - { - using var stream = _memoryMappedInfo.CreateReadableStream(); - using var reader = CreateTextReaderFromTemporaryStorage(stream); - - // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); - } - } - - public async Task ReadTextAsync(CancellationToken cancellationToken) - { - // There is a reason for implementing it like this: proper async implementation - // that reads the underlying memory mapped file stream in an asynchronous fashion - // doesn't actually work. Windows doesn't offer - // any non-blocking way to read from a memory mapped file; the underlying memcpy - // may block as the memory pages back in and that's something you have to live - // with. Therefore, any implementation that attempts to use async will still - // always be blocking at least one threadpool thread in the memcpy in the case - // of a page fault. Therefore, if we're going to be blocking a thread, we should - // just block one thread and do the whole thing at once vs. a fake "async" - // implementation which will continue to requeue work back to the thread pool. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - return ReadText(cancellationToken); - } - public void WriteText(SourceText text, CancellationToken cancellationToken) { if (_memoryMappedInfo != null) @@ -289,17 +338,6 @@ public async Task WriteTextAsync(SourceText text, CancellationToken cancellation WriteText(text, cancellationToken); } - - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) - { - var src = (char*)stream.PositionPointer; - - // BOM: Unicode, little endian - // Skip the BOM when creating the reader - Debug.Assert(*src == 0xFEFF); - - return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); - } } internal sealed class TemporaryStreamStorage @@ -323,21 +361,6 @@ public TemporaryStreamStorage( public long Offset => this.MemoryMappedInfo.Offset; public long Size => this.MemoryMappedInfo.Size; - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _memoryMappedInfo.CreateReadableStream(); - } - } - public void WriteStream(Stream stream, CancellationToken cancellationToken) { if (_memoryMappedInfo != null) @@ -363,66 +386,3 @@ public void WriteStream(Stream stream, CancellationToken cancellationToken) } } } - -internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength -{ - private char* _position; - private readonly char* _end; - - public DirectMemoryAccessStreamReader(char* src, int length) - : base(length) - { - RoslynDebug.Assert(src != null); - RoslynDebug.Assert(length >= 0); - - _position = src; - _end = _position + length; - } - - public override int Peek() - { - if (_position >= _end) - { - return -1; - } - - return *_position; - } - - public override int Read() - { - if (_position >= _end) - { - return -1; - } - - return *_position++; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0 || index >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0 || (index + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - count = Math.Min(count, (int)(_end - _position)); - if (count > 0) - { - Marshal.Copy((IntPtr)_position, buffer, index, count); - _position += count; - } - - return count; - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 460241f660790..2d432b715b2b2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -29,8 +31,29 @@ internal interface ITemporaryStorageStreamHandle internal interface ITemporaryStorageTextHandle { - public TemporaryStorageIdentifier Identifier { get; } + public TemporaryStorageTextIdentifier Identifier { get; } SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); } + +internal sealed record TemporaryStorageTextIdentifier( + string Name, long Offset, long Size, SourceHashAlgorithm ChecksumAlgorithm, Encoding? Encoding) +{ + public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64(), + (SourceHashAlgorithm)reader.ReadInt32(), + reader.ReadEncoding()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + writer.WriteInt32((int)ChecksumAlgorithm); + writer.WriteEncoding(Encoding); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs index 3d635adbe6c8d..ab3f73b119383 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs @@ -4,26 +4,26 @@ namespace Microsoft.CodeAnalysis.Host; -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -internal interface ITemporaryStorageWithName -{ - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Name shouldn't be nullable. +///// +///// TemporaryStorage can be used to read and write text to a temporary storage location. +///// +//internal interface ITemporaryStorageWithName +//{ +// // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 +// // Name shouldn't be nullable. - /// - /// Get name of the temporary storage - /// - string? Name { get; } +// /// +// /// Get name of the temporary storage +// /// +// string? Name { get; } - /// - /// Get offset of the temporary storage - /// - long Offset { get; } +// /// +// /// Get offset of the temporary storage +// /// +// long Offset { get; } - /// - /// Get size of the temporary storage - /// - long Size { get; } -} +// /// +// /// Get size of the temporary storage +// /// +// long Size { get; } +//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs index 9bef5f0738b8c..c53bf7dff6813 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs @@ -12,10 +12,10 @@ namespace Microsoft.CodeAnalysis; internal sealed partial class TrivialTemporaryStorageService { private sealed class TrivialStorageTextHandle( - TemporaryStorageIdentifier identifier, + TemporaryStorageTextIdentifier identifier, TextStorage storage) : ITemporaryStorageTextHandle { - public TemporaryStorageIdentifier Identifier => identifier; + public TemporaryStorageTextIdentifier Identifier => identifier; public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) => storage.ReadText(); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 113636d000d9d..b342ad6bbcce0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -23,7 +23,7 @@ public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, Canc { var storage = new TextStorage(); storage.WriteText(text); - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); + var identifier = new TemporaryStorageTextIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding); var handle = new TrivialStorageTextHandle(identifier, storage); return handle; } From f1faa2c2a2b7222f44ea2d5abe789384c08c8ae2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:22:58 -0700 Subject: [PATCH 0826/1047] Flesh out --- .../Test/Workspaces/TextFactoryTests.cs | 12 ++-- .../Serialization/SerializableSourceText.cs | 62 ++++++++----------- .../SerializerService_Reference.cs | 2 +- .../TemporaryStorageService.cs | 10 ++- .../TemporaryStorage/ITemporaryStorage.cs | 11 ---- .../ITemporaryStorageStreamHandle.cs | 33 ---------- .../ITemporaryStorageTextHandle.cs | 17 +++++ .../TemporaryStorageTextIdentifier.cs | 43 +++++++++++++ .../TrivialTemporaryStorageService.cs | 3 +- .../Solution/RecoverableTextAndVersion.cs | 25 ++++---- .../Workspace/Solution/TextDocumentState.cs | 4 +- .../TemporaryStorageServiceTests.cs | 28 +-------- 12 files changed, 117 insertions(+), 133 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index aa516e6d36f6e..377d303f6a210 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -85,13 +85,11 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); - // Create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); @@ -107,13 +105,11 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); - // Create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 37bc567b3dd73..c40bfa5cbc108 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -33,20 +33,20 @@ internal sealed class SerializableSourceText /// The storage location for . /// /// - /// Exactly one of or will be non-. + /// Exactly one of or will be non-. /// - private readonly TemporaryTextStorage? _storage; + private readonly TemporaryStorageTextHandle? _storageHandle; /// /// The in the current process. /// /// - /// + /// /// private readonly SourceText? _text; /// - /// Weak reference to a SourceText computed from . Useful so that if multiple requests + /// Weak reference to a SourceText computed from . Useful so that if multiple requests /// come in for the source text, the same one can be returned as long as something is holding it alive. /// private readonly WeakReference _computedText = new(target: null); @@ -56,26 +56,26 @@ internal sealed class SerializableSourceText /// public readonly Checksum ContentChecksum; - public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) - : this(storage, text: null, contentHash) + public SerializableSourceText(TemporaryStorageTextHandle storageHandle) + : this(storageHandle, text: null, storageHandle.Identifier.ContentHash) { } public SerializableSourceText(SourceText text, ImmutableArray contentHash) - : this(storage: null, text, contentHash) + : this(storageHandle: null, text, contentHash) { } - private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, ImmutableArray contentHash) + private SerializableSourceText(TemporaryStorageTextHandle? storageHandle, SourceText? text, ImmutableArray contentHash) { - Debug.Assert(storage is null != text is null); + Debug.Assert(storageHandle is null != text is null); - _storage = storage; + _storageHandle = storageHandle; _text = text; ContentChecksum = Checksum.Create(contentHash); #if DEBUG - var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; + var computedContentHash = TryGetText()?.GetContentHash() ?? _storageHandle!.Identifier.ContentHash; Debug.Assert(contentHash.SequenceEqual(computedContentHash)); #endif } @@ -95,7 +95,7 @@ public async ValueTask GetTextAsync(CancellationToken cancellationTo return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = await _storage!.ReadTextAsync(cancellationToken).ConfigureAwait(false); + text = await _storageHandle!.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); _computedText.SetTarget(text); return text; } @@ -107,7 +107,7 @@ public SourceText GetText(CancellationToken cancellationToken) return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = _storage!.ReadText(cancellationToken); + text = _storageHandle!.ReadFromTemporaryStorage(cancellationToken); _computedText.SetTarget(text); return text; } @@ -120,10 +120,10 @@ public static ValueTask FromTextDocumentStateAsync( // If we're already pointing at a serializable loader, we can just use that directly. return new(serializableLoader.SerializableSourceText); } - else if (state.Storage is TemporaryTextStorage storage) + else if (state.StorageHandle is TemporaryStorageTextHandle storageHandle) { // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. - return new(new SerializableSourceText(storage, storage.ContentHash)); + return new(new SerializableSourceText(storageHandle)); } else { @@ -142,58 +142,48 @@ public static ValueTask FromTextDocumentStateAsync( public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (_storage is not null) + if (_storageHandle is not null) { - writer.WriteInt32((int)_storage.ChecksumAlgorithm); - writer.WriteEncoding(_storage.Encoding); - writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storage.ContentHash)!); - writer.WriteInt32((int)SerializationKinds.MemoryMapFile); - writer.WriteString(_storage.Name); - writer.WriteInt64(_storage.Offset); - writer.WriteInt64(_storage.Size); + _storageHandle.Identifier.WriteTo(writer); } else { RoslynDebug.AssertNotNull(_text); + writer.WriteInt32((int)SerializationKinds.Bits); writer.WriteInt32((int)_text.ChecksumAlgorithm); writer.WriteEncoding(_text.Encoding); writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_text.GetContentHash())!); - writer.WriteInt32((int)SerializationKinds.Bits); _text.WriteTo(writer, cancellationToken); } } public static SerializableSourceText Deserialize( ObjectReader reader, - ITemporaryStorageServiceInternal storageService, + TemporaryStorageService storageService, ITextFactoryService textService, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); - var encoding = reader.ReadEncoding(); - var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); - var kind = (SerializationKinds)reader.ReadInt32(); Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); if (kind == SerializationKinds.MemoryMapFile) { - var storage2 = (TemporaryStorageService)storageService; - - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); + var identifier = TemporaryStorageTextIdentifier.ReadFrom(reader); + var storageHandle = storageService.GetHandle(identifier); - var storage = storage2.AttachTemporaryTextStorage(name, offset, size, checksumAlgorithm, encoding, contentHash); - return new SerializableSourceText(storage, contentHash); + return new SerializableSourceText(storageHandle); } else { + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + return new SerializableSourceText( SourceTextExtensions.ReadFrom(textService, reader, encoding, checksumAlgorithm, cancellationToken), contentHash); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index e958468cd5861..344aaf18f858f 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -385,7 +385,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - var storageHandle = _storageService.GetHandle(storageIdentifier); + var storageHandle = TemporaryStorageService.GetHandle(storageIdentifier); return ReadModuleMetadataFromStorage(storageHandle); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index c349cadcd41e4..e09ba20e21cf2 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -124,7 +124,7 @@ public async Task WriteToTemporaryStorageAsync(Sourc private TemporaryStorageTextHandle CreateHandleFromStorage(TemporaryTextStorage storage) { var identifier = new TemporaryStorageTextIdentifier( - storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding); + storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding, storage.ContentHash); return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); } @@ -146,6 +146,12 @@ internal static TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifie return new(memoryMappedFile, storageIdentifier); } + internal TemporaryStorageTextHandle GetHandle(TemporaryStorageTextIdentifier storageIdentifier) + { + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(this, memoryMappedFile, storageIdentifier); + } + /// /// Allocate shared storage of a specified size. /// @@ -251,7 +257,7 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM #endif } - public sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName + private sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; private SourceHashAlgorithm _checksumAlgorithm; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index 8e4dde76b2938..e19dbf62c9fc2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -29,14 +29,3 @@ public interface ITemporaryStreamStorage : IDisposable void WriteStream(Stream stream, CancellationToken cancellationToken = default); Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); } - -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -//internal interface ITemporaryTextStorageInternal -//{ -// SourceText ReadText(CancellationToken cancellationToken = default); -// Task ReadTextAsync(CancellationToken cancellationToken = default); -// void WriteText(SourceText text, CancellationToken cancellationToken = default); -// Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); -//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 2d432b715b2b2..418a8a7fe50cf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -3,11 +3,7 @@ // See the LICENSE file in the project root for more information. using System.IO; -using System.Text; using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -28,32 +24,3 @@ internal interface ITemporaryStorageStreamHandle /// Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); } - -internal interface ITemporaryStorageTextHandle -{ - public TemporaryStorageTextIdentifier Identifier { get; } - - SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); - Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); -} - -internal sealed record TemporaryStorageTextIdentifier( - string Name, long Offset, long Size, SourceHashAlgorithm ChecksumAlgorithm, Encoding? Encoding) -{ - public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) - => new( - reader.ReadRequiredString(), - reader.ReadInt64(), - reader.ReadInt64(), - (SourceHashAlgorithm)reader.ReadInt32(), - reader.ReadEncoding()); - - public void WriteTo(ObjectWriter writer) - { - writer.WriteString(Name); - writer.WriteInt64(Offset); - writer.WriteInt64(Size); - writer.WriteInt32((int)ChecksumAlgorithm); - writer.WriteEncoding(Encoding); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs new file mode 100644 index 0000000000000..eb0fc241b1417 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Host; + +internal interface ITemporaryStorageTextHandle +{ + public TemporaryStorageTextIdentifier Identifier { get; } + + SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); + Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs new file mode 100644 index 0000000000000..26489a52b21bc --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Identifier for a placed in a segment of temporary storage (generally a memory mapped file). +/// Can be used to identify that segment across processes, allowing for efficient sharing of data. +/// +internal sealed record TemporaryStorageTextIdentifier( + string Name, + long Offset, + long Size, + SourceHashAlgorithm ChecksumAlgorithm, + Encoding? Encoding, + ImmutableArray ContentHash) +{ + public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64(), + (SourceHashAlgorithm)reader.ReadInt32(), + reader.ReadEncoding(), + ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray())); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + writer.WriteInt32((int)ChecksumAlgorithm); + writer.WriteEncoding(Encoding); + writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(ContentHash)!); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index b342ad6bbcce0..c9b070337e0a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -23,7 +23,8 @@ public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, Canc { var storage = new TextStorage(); storage.WriteText(text); - var identifier = new TemporaryStorageTextIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding); + var identifier = new TemporaryStorageTextIdentifier( + Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding, text.GetContentHash()); var handle = new TrivialStorageTextHandle(identifier, storage); return handle; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index b4b327a618e44..7314888e4c9ba 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -48,8 +48,8 @@ private bool TryGetInitialSourceOrRecoverableText([NotNullWhen(true)] out ITextA public TextLoader? TextLoader => (_initialSourceOrRecoverableText as ITextAndVersionSource)?.TextLoader; - public ITemporaryTextStorageInternal? Storage - => (_initialSourceOrRecoverableText as RecoverableText)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (_initialSourceOrRecoverableText as RecoverableText)?.StorageHandle; public bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value) { @@ -143,7 +143,7 @@ private sealed partial class RecoverableText public readonly ITextAndVersionSource? InitialSource; public readonly LoadTextOptions LoadTextOptions; - public ITemporaryTextStorageInternal? _storage; + public ITemporaryStorageTextHandle? _storageHandle; public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersion, LoadTextOptions options, SolutionServices services) { @@ -166,37 +166,36 @@ public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersi public TextAndVersion ToTextAndVersion(SourceText text) => TextAndVersion.Create(text, Version, LoadDiagnostic); - public ITemporaryTextStorageInternal? Storage => _storage; + public ITemporaryStorageTextHandle? StorageHandle => _storageHandle; private async Task RecoverAsync(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverTextAsync, cancellationToken)) { - return await _storage.ReadTextAsync(cancellationToken).ConfigureAwait(false); + return await _storageHandle.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); } } private SourceText Recover(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverText, cancellationToken)) { - return _storage.ReadText(cancellationToken); + return _storageHandle.ReadFromTemporaryStorage(cancellationToken); } } private async Task SaveAsync(SourceText text, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(_storage == null); // Cannot save more than once + Contract.ThrowIfFalse(_storageHandle == null); // Cannot save more than once - var storage = _storageService.CreateTemporaryTextStorage(); - await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); + var handle = await _storageService.WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); - // make sure write is done before setting _storage field - Interlocked.CompareExchange(ref _storage, storage, null); + // make sure write is done before setting _storageHandle field + Interlocked.CompareExchange(ref _storageHandle, handle, null); // Only set _initialValue to null once writing to the storage service completes fully. If the save did not // complete, we want to keep it around to service future requests. Once we do clear out this value, then diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 53c6008610ccc..63fa8cb9f446d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -82,8 +82,8 @@ private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, : new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services); } - public ITemporaryTextStorageInternal? Storage - => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (TextAndVersionSource as RecoverableTextAndVersion)?.StorageHandle; public bool TryGetText([NotNullWhen(returnValue: true)] out SourceText? text) { diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 2bd1e0f417690..053cad8235405 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -73,41 +73,17 @@ public void TestTemporaryStorageStream() private static void TestTemporaryStorage(ITemporaryStorageServiceInternal temporaryStorageService, SourceText text) { - // create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); - // write text into it - temporaryStorage.WriteTextAsync(text).Wait(); + var handle = temporaryStorageService.WriteToTemporaryStorage(text, CancellationToken.None); // read text back from it - var text2 = temporaryStorage.ReadTextAsync().Result; + var text2 = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); Assert.Equal(text.Encoding, text2.Encoding); } - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryTextStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryTextStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadText()); - Assert.Throws(() => storage.ReadTextAsync().Result); - - // write a normal string - var text = SourceText.From(new string(' ', 4096) + "public class A {}"); - storage.WriteTextAsync(text).Wait(); - - // Writing multiple times is not allowed - Assert.Throws(() => storage.WriteText(text)); - Assert.Throws(() => storage.WriteTextAsync(text).Wait()); - } - [ConditionalFact(typeof(WindowsOnly))] public void TestZeroLengthStreams() { From 4d7b1fdcbdbe615ad3e5fe9219499f17ff059a70 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Wed, 24 Apr 2024 11:29:48 -0700 Subject: [PATCH 0827/1047] Update referenced MSBuild to 17.3.4 (#73113) * Update referenced MSBuild to 17.3.4 * Bump structured log viewer library version To account for newer binlog versions emitted by build tools. * Back down to latest on feed --------- Co-authored-by: Rainer Sigwald --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 62d569171ed46..7b9c7fd7f9bfe 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -32,7 +32,7 @@ 17.8.9-preview 17.7.37349 - 16.10.0 + 17.3.4 17.5.0 - + From 8293404bdf57d55e40baa79768cabb8c85041e75 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 25 Apr 2024 11:43:22 -0700 Subject: [PATCH 0848/1047] CR feedback --- .../AlwaysActivateInProcLanguageClient.cs | 4 +-- .../AbstractDocumentPullDiagnosticHandler.cs | 14 ++------ .../DiagnosticSourceManager.cs | 27 +++++++++------ .../IDiagnosticSourceManager.cs | 33 +++++++++++-------- .../IDiagnosticSourceProvider.cs | 6 ++-- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 3 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 23 +++++-------- .../DocumentTaskDiagnosticSourceProvider.cs | 2 +- .../WorkspaceTaskDiagnosticSourceProvider.cs | 2 +- .../Contracts/HotReloadRequestContext.cs | 2 +- .../Contracts/IHotReloadDiagnosticSource.cs | 2 +- .../Internal/HotReloadDiagnosticManager.cs | 20 ++++++----- .../Internal/HotReloadDiagnosticSource.cs | 17 +++++----- .../HotReloadDiagnosticSourceProvider.cs | 25 ++++++++++---- .../InternalAPI.Unshipped.txt | 19 ++++++++--- 15 files changed, 111 insertions(+), 88 deletions(-) rename src/Features/LanguageServer/Protocol/Handler/{Diagnostics/DiagnosticSourceProviders => Tasks}/DocumentTaskDiagnosticSourceProvider.cs (99%) rename src/Features/LanguageServer/Protocol/Handler/{Task => Tasks}/WorkspaceTaskDiagnosticSourceProvider.cs (99%) diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 60eb7369d5785..dacbc7633a275 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -74,8 +74,8 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.DiagnosticProvider ??= new(); // VS does not distinguish between document and workspace diagnostics, so we need to merge them. - var diagnosticSourceNames = _diagnosticSourceManager.GetSourceNames(isDocument: true) - .Concat(_diagnosticSourceManager.GetSourceNames(isDocument: false)) + var diagnosticSourceNames = _diagnosticSourceManager.GetDocumentSourceProviderNames() + .Concat(_diagnosticSourceManager.GetWorkspaceSourceProviderNames()) .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 37df98082f052..106d2dc6bfc78 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -29,14 +29,6 @@ internal abstract class AbstractDocumentPullDiagnosticHandler> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) - { - if (GetOpenDocument(context) is null) - return new([]); - - return DiagnosticSourceManager.CreateDocumentDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); - } - - private static TextDocument? GetOpenDocument(RequestContext context) { // Note: context.Document may be null in the case where the client is asking about a document that we have // since removed from the workspace. In this case, we don't really have anything to process. @@ -48,15 +40,15 @@ protected override ValueTask> GetOrderedDiagno if (textDocument is null) { context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return null; + return new([]); } if (!context.IsTracking(textDocument.GetURI())) { context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return null; + return new([]); } - return textDocument; + return DiagnosticSourceManager.CreateDocumentDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs index ed057b7745b3e..6058141a7ec4f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -46,27 +46,31 @@ public DiagnosticSourceManager([ImportMany] IEnumerable - public ImmutableArray GetSourceNames(bool isDocument) - => (isDocument ? _nameToDocumentProviderMap : _nameToWorkspaceProviderMap).Keys.ToImmutableArray(); + public ImmutableArray GetDocumentSourceProviderNames() + => _nameToDocumentProviderMap.Keys.ToImmutableArray(); - public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken) - => CreateDiagnosticSourcesAsync(context, sourceName, _nameToDocumentProviderMap, isDocument: true, cancellationToken); + /// + public ImmutableArray GetWorkspaceSourceProviderNames() + => _nameToWorkspaceProviderMap.Keys.ToImmutableArray(); + + public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToDocumentProviderMap, isDocument: true, cancellationToken); - public ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken) - => CreateDiagnosticSourcesAsync(context, sourceName, _nameToWorkspaceProviderMap, isDocument: false, cancellationToken); + public ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToWorkspaceProviderMap, isDocument: false, cancellationToken); private static async ValueTask> CreateDiagnosticSourcesAsync( RequestContext context, - string? sourceName, + string? providerName, ImmutableDictionary nameToProviderMap, bool isDocument, CancellationToken cancellationToken) { - if (sourceName != null) + if (providerName != null) { // VS does not distinguish between document and workspace sources. Thus it can request // document diagnostics with workspace source name. We need to handle this case. - if (nameToProviderMap.TryGetValue(sourceName, out var provider)) + if (nameToProviderMap.TryGetValue(providerName, out var provider)) return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); return []; @@ -105,7 +109,10 @@ public static ImmutableArray AggregateSourcesIfNeeded(Immutab } else { - // For workspace we need to group sources by source id and IsLiveSource + // We ASSUME that all sources with the same ProjectOrDocumentID and IsLiveSource + // will have same value for GetDocumentIdentifier and GetProject(). Thus can be + // aggregated in a single source which will return same values. See + // AggregatedDocumentDiagnosticSource implementation for more details. sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) .ToImmutableArray(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs index 0c7eddd434578..51e0ef8ff0e10 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs @@ -9,29 +9,34 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; /// -/// Manages the diagnostic sources that provide diagnostics for the language server. +/// Provides centralized/singleton management of MEF based s. +/// Consumers - like diagnostic handlers - use it to get diagnostics from one or more providers. /// internal interface IDiagnosticSourceManager { /// - /// Returns the names of all the sources that provide diagnostics for the given . + /// Returns the names of document level s. /// - /// for document sources and for workspace sources. - ImmutableArray GetSourceNames(bool isDocument); + ImmutableArray GetDocumentSourceProviderNames(); /// - /// Creates document diagnostic sources for the given . + /// Returns the names of workspace level s. /// - /// The context. - /// Source name. - /// The cancellation token. - ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken); + ImmutableArray GetWorkspaceSourceProviderNames(); /// - /// Creates workspace diagnostic sources for the given . + /// Creates document diagnostic sources for the given . /// - /// The context. - /// Source name. - /// The cancellation token. - ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken); + /// + /// Optional provider name. If then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); + + /// + /// Creates workspace diagnostic sources for the given . + /// + /// + /// Optional provider name. If not specified then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs index bcac55c7deb64..daeb1b4d71aee 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal interface IDiagnosticSourceProvider { /// - /// True if this provider is for documents, false if it is for workspace. + /// if this provider is for documents, if it is for workspace. /// bool IsDocument { get; } @@ -26,8 +26,8 @@ internal interface IDiagnosticSourceProvider /// /// Creates the diagnostic sources. /// - /// The context. - /// The cancellation token. + /// + /// A cancellation token that can be used to cancel the request processing. ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index f2a7244eeeec8..a8332219d1d12 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -23,7 +23,8 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. - var sources = DiagnosticSourceManager.GetSourceNames(isDocument: true).Where(source => source != PullDiagnosticCategories.Task); + // Task diagnostics shouldn't be reported through VSCode (it has its own task stuff). Additional cleanup needed. + var sources = DiagnosticSourceManager.GetDocumentSourceProviderNames().Where(source => source != PullDiagnosticCategories.Task); var registrations = sources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index cf53f3cbe8846..2e34f2dbccf03 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -15,26 +15,21 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { - var sources = DiagnosticSourceManager.GetSourceNames(isDocument: false); + var providerNames = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams { - Registrations = sources.Select(FromSourceName).ToArray() + Registrations = providerNames.Select(name => new Registration + { + // Due to https://github.com/microsoft/language-server-protocol/issues/1723 + // we need to use textDocument/diagnostic instead of workspace/diagnostic + Method = Methods.TextDocumentDiagnosticName, + Id = name, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = name, InterFileDependencies = true, WorkDoneProgress = true } + }).ToArray() }, cancellationToken).ConfigureAwait(false); - - Registration FromSourceName(string sourceName) - { - return new() - { - // Due to https://github.com/microsoft/language-server-protocol/issues/1723 - // we need to use textDocument/diagnostic instead of workspace/diagnostic - Method = Methods.TextDocumentDiagnosticName, - Id = sourceName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkDoneProgress = true } - }; - } } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs similarity index 99% rename from src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs index 5276a1ff9a98b..6b63bdf91f220 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] diff --git a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs similarity index 99% rename from src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs index 6620285cfea20..53b1bf7e03cd5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs @@ -13,7 +13,7 @@ using Microsoft.CodeAnalysis.TaskList; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs index 6e879f2535158..c7b511ee906c2 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -internal class HotReloadRequestContext(RequestContext context) +internal sealed class HotReloadRequestContext(RequestContext context) { internal LSP.ClientCapabilities ClientCapabilities => context.GetRequiredClientCapabilities(); public TextDocument? TextDocument => context.TextDocument; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs index 05f1cee575b41..85a8004f1db8d 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -16,7 +16,7 @@ internal interface IHotReloadDiagnosticSource /// /// Text document for which diagnostics are provided. /// - TextDocument TextDocument { get; } + DocumentId DocumentId { get; } /// /// Provides list of diagnostics for the given document. diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index 8705fcc27b18f..8a6071ea4228a 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -18,11 +18,12 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { private readonly object _syncLock = new(); - private ImmutableArray _providers = ImmutableArray.Empty; - ImmutableArray IHotReloadDiagnosticManager.Providers => _providers; - void IHotReloadDiagnosticManager.RequestRefresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); + public ImmutableArray Providers { get; private set; } = []; - void IHotReloadDiagnosticManager.Register(IEnumerable providers) + public void RequestRefresh() + => diagnosticsRefresher.RequestWorkspaceRefresh(); + + public void Register(IEnumerable providers) { // We use array instead of e.g. HashSet because we expect the number of sources to be small. // Usually 2, one workspace and one document provider. @@ -30,18 +31,19 @@ void IHotReloadDiagnosticManager.Register(IEnumerable providers) + public void Unregister(IEnumerable providers) { lock (_syncLock) { - foreach (var provider in providers) - _providers = _providers.Remove(provider); + foreach (var provider in Providers) + Providers = Providers.Remove(provider); } } + } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index c7b0e58584388..64d157b4a1343 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -15,19 +15,18 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source) : IDiagnosticSource +internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source, TextDocument textDocument) : IDiagnosticSource { - async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false); - var document = source.TextDocument; - var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, document)).ToImmutableArray(); + var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, textDocument)).ToImmutableArray(); return result; } - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = source.TextDocument.GetURI() }; - ProjectOrDocumentId IDiagnosticSource.GetId() => new(source.TextDocument.Id); - Project IDiagnosticSource.GetProject() => source.TextDocument.Project; - bool IDiagnosticSource.IsLiveSource() => true; - string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {source.TextDocument.FilePath ?? source.TextDocument.Name} in {source.TextDocument.Project.Name}"; + public TextDocumentIdentifier? GetDocumentIdentifier() => new() { Uri = textDocument.GetURI() }; + public ProjectOrDocumentId GetId() => new(textDocument.Id); + public Project GetProject() => textDocument.Project; + public bool IsLiveSource() => true; + public string ToDisplayString() => $"{this.GetType().Name}: {textDocument.FilePath ?? textDocument.Name} in {textDocument.Project.Name}"; } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs index 2b8acf9787caa..495e586ea6f13 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; @@ -19,11 +17,16 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager diagnosticManager, bool isDocument) : IDiagnosticSourceProvider { - string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; - bool IDiagnosticSourceProvider.IsDocument => isDocument; + public string Name => "HotReloadDiagnostics"; + public bool IsDocument => isDocument; - async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { + if (context.Solution is not Solution solution) + { + return ImmutableArray.Empty; + } + var hotReloadContext = new HotReloadRequestContext(context); using var _ = ArrayBuilder.GetInstance(out var sources); foreach (var provider in diagnosticManager.Providers) @@ -31,7 +34,17 @@ async ValueTask> IDiagnosticSourceProvider.Cre if (provider.IsDocument == isDocument) { var hotReloadSources = await provider.CreateDiagnosticSourcesAsync(hotReloadContext, cancellationToken).ConfigureAwait(false); - sources.AddRange(hotReloadSources.Select(s => new HotReloadDiagnosticSource(s))); + foreach (var hotReloadSource in hotReloadSources) + { + // Look for additional document first. Currently most common hot reload diagnostics come from *.xaml files. + TextDocument? textDocument = + solution.GetAdditionalDocument(hotReloadSource.DocumentId) ?? + solution.GetDocument(hotReloadSource.DocumentId); + if (textDocument != null) + { + sources.Add(new HotReloadDiagnosticSource(hotReloadSource, textDocument)); + } + } } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 70c2918b8f69c..019251a6dad44 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,4 +1,3 @@ -abstract Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.ClientCapabilities.get -> Roslyn.LanguageServer.Protocol.ClientCapabilities! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.HotReloadRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void @@ -11,8 +10,8 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiag Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.RequestRefresh() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.IsDocument.get -> bool @@ -24,13 +23,23 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnos Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDocumentIdentifier() -> Roslyn.LanguageServer.Protocol.TextDocumentIdentifier? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetId() -> Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.ProjectOrDocumentId +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetProject() -> Microsoft.CodeAnalysis.Project! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source, Microsoft.CodeAnalysis.TextDocument! textDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.IsLiveSource() -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.ToDisplayString() -> string! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager, bool isDocument) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.IsDocument.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.Name.get -> string! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void From 845f1e3f39952db75e1902897b55486a2ba06f94 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 11:48:25 -0700 Subject: [PATCH 0849/1047] Inline --- .../Solution/DocumentState_LinkedFileReuse.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 9353d1f140a87..fea908b2679fc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -55,8 +55,10 @@ public DocumentState UpdateTextAndTreeContents( var textAndVersionSource = this.TextAndVersionSource; var treeSource = this.TreeSource; - var newTreeSource = GetReuseTreeSource( - filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer); + var newTreeSource = AsyncLazy.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); return new DocumentState( languageServices, @@ -67,22 +69,6 @@ public DocumentState UpdateTextAndTreeContents( LoadTextOptions, newTreeSource); - static AsyncLazy GetReuseTreeSource( - string filePath, - LanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - AsyncLazy treeSource, - ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, - bool forceEvenIfTreesWouldDiffer) - { - return AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - } - static bool TryReuseSiblingRoot( string filePath, LanguageServices languageServices, From 7b226e9ad9a152d00169128e715d3fa0c40dabf0 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 25 Apr 2024 11:51:50 -0700 Subject: [PATCH 0850/1047] Remove unneded docs --- .../DiagnosticSourceProviders/DiagnosticSourceManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs index 6058141a7ec4f..1b870ac2dc45f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -45,11 +45,9 @@ public DiagnosticSourceManager([ImportMany] IEnumerable kvp.Name, kvp => kvp); } - /// public ImmutableArray GetDocumentSourceProviderNames() => _nameToDocumentProviderMap.Keys.ToImmutableArray(); - /// public ImmutableArray GetWorkspaceSourceProviderNames() => _nameToWorkspaceProviderMap.Keys.ToImmutableArray(); From 3dba8a015d7042ce3dd6df6109507700129222ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 12:13:13 -0700 Subject: [PATCH 0851/1047] in progress --- .../Workspace/Solution/DocumentState.cs | 28 ++++---- .../Solution/DocumentState_LinkedFileReuse.cs | 64 +++++++++++++------ .../Solution/SourceGeneratedDocumentState.cs | 2 +- .../AsyncLazyTreeAndVersionSource.cs | 44 +++++++++++++ .../ITextAndVersionSource.cs | 0 .../VersionSource/ITreeAndVersionSource.cs | 20 ++++++ .../LoadableTextAndVersionSource.cs | 0 ...coverableTextAndVersion.RecoverableText.cs | 0 .../RecoverableTextAndVersion.cs | 0 9 files changed, 125 insertions(+), 33 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/ITextAndVersionSource.cs (100%) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/LoadableTextAndVersionSource.cs (100%) rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/RecoverableTextAndVersion.RecoverableText.cs (100%) rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/RecoverableTextAndVersion.cs (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 9c92c71478ee4..8a4ca25d9b8b0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -31,7 +31,7 @@ internal partial class DocumentState : TextDocumentState private readonly ParseOptions? _options; // null if the document doesn't support syntax trees: - private readonly AsyncLazy? _treeSource; + private readonly ITreeAndVersionSource? _treeSource; protected DocumentState( LanguageServices languageServices, @@ -40,7 +40,7 @@ protected DocumentState( ParseOptions? options, ITextAndVersionSource textSource, LoadTextOptions loadTextOptions, - AsyncLazy? treeSource) + ITreeAndVersionSource? treeSource) : base(languageServices.SolutionServices, documentServiceProvider, attributes, textSource, loadTextOptions) { Contract.ThrowIfFalse(_options is null == _treeSource is null); @@ -79,7 +79,7 @@ public DocumentState( } } - public AsyncLazy? TreeSource => _treeSource; + public ITreeAndVersionSource? TreeSource => _treeSource; [MemberNotNullWhen(true, nameof(_treeSource))] [MemberNotNullWhen(true, nameof(TreeSource))] @@ -97,7 +97,7 @@ public SourceCodeKind SourceCodeKind public bool IsGenerated => Attributes.IsGenerated; - protected static AsyncLazy CreateLazyFullyParsedTree( + protected static ITreeAndVersionSource CreateLazyFullyParsedTree( ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, string? filePath, @@ -105,7 +105,7 @@ protected static AsyncLazy CreateLazyFullyParsedTree( LanguageServices languageServices, PreservationMode mode = PreservationMode.PreserveValue) { - return AsyncLazy.Create( + return AsyncLazyTreeAndVersionSource.Create( static (arg, c) => FullyParseTreeAsync(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), static (arg, c) => FullyParseTree(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), arg: (newTextSource, loadTextOptions, filePath, options, languageServices, mode)); @@ -163,19 +163,19 @@ private static TreeAndVersion CreateTreeAndVersion( return new TreeAndVersion(tree, textAndVersion.Version); } - private static AsyncLazy CreateLazyIncrementallyParsedTree( - AsyncLazy oldTreeSource, + private static ITreeAndVersionSource CreateLazyIncrementallyParsedTree( + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions) { - return AsyncLazy.Create( + return AsyncLazyTreeAndVersionSource.Create( static (arg, c) => IncrementallyParseTreeAsync(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), static (arg, c) => IncrementallyParseTree(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), arg: (oldTreeSource, newTextSource, loadTextOptions)); } private static async Task IncrementallyParseTreeAsync( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -197,7 +197,7 @@ private static async Task IncrementallyParseTreeAsync( } private static TreeAndVersion IncrementallyParseTree( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -346,7 +346,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso throw new InvalidOperationException(); } - AsyncLazy? newTreeSource = null; + ITreeAndVersionSource? newTreeSource = null; // Optimization: if we are only changing preprocessor directives, and we've already parsed the existing tree // and it didn't have any, we can avoid a reparse since the tree will be parsed the same. @@ -368,7 +368,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso } if (newTree is not null) - newTreeSource = AsyncLazy.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); + newTreeSource = AsyncLazyTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); } // If we weren't able to reuse in a smart way, just reparse @@ -457,7 +457,7 @@ public DocumentState UpdateFilePath(string? filePath) protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { - AsyncLazy? newTreeSource; + ITreeAndVersionSource? newTreeSource; if (!SupportsSyntaxTree) { @@ -528,7 +528,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) _options, textSource: text, LoadTextOptions, - treeSource: AsyncLazy.Create(treeAndVersion)); + treeSource: AsyncLazyTreeAndVersionSource.Create(treeAndVersion)); // use static method so we don't capture references to this static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index fea908b2679fc..3a54b69e7cec0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -13,6 +13,26 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { + private sealed class LinkedFileTreeAndVersionSource( + ITextAndVersionSource siblingTextSource, + ITreeAndVersionSource siblingTreeSource, + bool forceEvenIfTreesWouldDiffer, + AsyncLazy lazyComputation) : ITreeAndVersionSource + { + public readonly ITextAndVersionSource SiblingTextSource = siblingTextSource; + public readonly ITreeAndVersionSource SiblingTreeSource = siblingTreeSource; + public readonly bool ForceEvenIfTreesWouldDiffer = forceEvenIfTreesWouldDiffer; + + public Task GetValueAsync(CancellationToken cancellationToken) + => lazyComputation.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => lazyComputation.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => lazyComputation.TryGetValue(out value); + } + /// /// Returns a new instance of this document state that points to as the /// text contents of the document, and which will produce a syntax tree that reuses from public DocumentState UpdateTextAndTreeContents( ITextAndVersionSource siblingTextSource, - AsyncLazy? siblingTreeSource, + ITreeAndVersionSource? siblingTreeSource, bool forceEvenIfTreesWouldDiffer) { if (!SupportsSyntaxTree) @@ -38,6 +58,17 @@ public DocumentState UpdateTextAndTreeContents( Contract.ThrowIfNull(siblingTreeSource); + // We don't want to point at a long chain of linked files, deferring to each next link of the chain to + // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is + // already linked to some other file, just point directly at that file instead. This can help when there are + // pathological cases (like a large solution with a single file linked into every project in the solution). + while (siblingTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource && + linkedFileTreeAndVersionSource.SiblingTextSource == siblingTextSource && + linkedFileTreeAndVersionSource.ForceEvenIfTreesWouldDiffer == forceEvenIfTreesWouldDiffer) + { + siblingTreeSource = linkedFileTreeAndVersionSource.SiblingTreeSource; + } + // Always pass along the sibling text. We will always be in sync with that. // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as @@ -46,22 +77,19 @@ public DocumentState UpdateTextAndTreeContents( // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. - // copy data from this entity, and pass to static helper, so we don't keep this green node alive. - - var filePath = this.Attributes.SyntaxTreeFilePath; - var languageServices = this.LanguageServices; - var loadTextOptions = this.LoadTextOptions; - var parseOptions = this.ParseOptions; - var textAndVersionSource = this.TextAndVersionSource; - var treeSource = this.TreeSource; + var lazyComputation = AsyncLazy.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (Attributes.SyntaxTreeFilePath, LanguageServices, LoadTextOptions, ParseOptions, TreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - var newTreeSource = AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + var newTreeSource = new LinkedFileTreeAndVersionSource( + siblingTextSource, + siblingTreeSource, + forceEvenIfTreesWouldDiffer, + lazyComputation); return new DocumentState( - languageServices, + LanguageServices, Services, Attributes, _options, @@ -172,9 +200,9 @@ static async Task TryReuseSiblingTreeAsync( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { @@ -195,9 +223,9 @@ static TreeAndVersion TryReuseSiblingTree( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs index e404b3a91c413..cb1591b0eb256 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs @@ -105,7 +105,7 @@ private SourceGeneratedDocumentState( ITextAndVersionSource textSource, SourceText text, LoadTextOptions loadTextOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, Lazy lazyContentHash, DateTime generationDateTime) : base(languageServices, documentServiceProvider, attributes, options, textSource, loadTextOptions, treeSource) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs new file mode 100644 index 0000000000000..6780ff809213c --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Simple implementation of backed by an opaque ."/> +/// +internal sealed class AsyncLazyTreeAndVersionSource : ITreeAndVersionSource +{ + private readonly AsyncLazy _source; + + private AsyncLazyTreeAndVersionSource(AsyncLazy source) + { + _source = source; + } + + public Task GetValueAsync(CancellationToken cancellationToken) + => _source.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => _source.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => _source.TryGetValue(out value); + + public static AsyncLazyTreeAndVersionSource Create( + Func> asynchronousComputeFunction, + Func? synchronousComputeFunction, TArg arg) + { + return new(AsyncLazy.Create(asynchronousComputeFunction, synchronousComputeFunction, arg)); + } + + public static AsyncLazyTreeAndVersionSource Create(TreeAndVersion source) + => new(AsyncLazy.Create(source)); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs new file mode 100644 index 0000000000000..75e0d0c670b33 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis; + +/// +/// Similar to , but for trees. Allows hiding (or introspecting) the details of how +/// a tree is created for a particular document. +/// +internal interface ITreeAndVersionSource +{ + Task GetValueAsync(CancellationToken cancellationToken); + TreeAndVersion GetValue(CancellationToken cancellationToken); + bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs From f3cc5316d45d772c38587ff3ba27cbf0b1956806 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 12:47:27 -0700 Subject: [PATCH 0852/1047] Demonstrate stack overflow --- .../CoreTest/SolutionTests/SolutionTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 67e9cabedec10..61fd430076efe 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5080,5 +5080,36 @@ public async Task TestFrozenPartialSolutionOtherLanguage() var frozenCompilation = await frozenProject.GetCompilationAsync(); Assert.Null(frozenCompilation); } + + [Fact] + public async Task TestLargeLinkedFileChain() + { + using var workspace = CreateWorkspace(); + + var project1 = workspace.CurrentSolution + .AddProject($"Project1", $"Project1", LanguageNames.CSharp) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId1 = project1.DocumentIds.Single(); + + var project2 = project1.Solution + .AddProject($"Project2", $"Project2", LanguageNames.CSharp) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId2 = project2.DocumentIds.Single(); + + workspace.SetCurrentSolution( + _ => project2.Solution, + (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null)); + + for (var i = 0; i < 4000; i++) + { + workspace.SetCurrentSolution( + old => old.WithDocumentText(documentId1, SourceText.From($"//{new string('.', i)}//")), + (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); + } + + var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); + + var tree = await document2.GetSyntaxTreeAsync(); + } } } From 877ade27cad089c377c09ce966d02e3b552a91f7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 12:57:31 -0700 Subject: [PATCH 0853/1047] work --- .../Solution/DocumentState_LinkedFileReuse.cs | 25 +++++++------------ .../CoreTest/SolutionTests/SolutionTests.cs | 1 + 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 3a54b69e7cec0..feb856fb3d019 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -14,14 +14,10 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { private sealed class LinkedFileTreeAndVersionSource( - ITextAndVersionSource siblingTextSource, - ITreeAndVersionSource siblingTreeSource, - bool forceEvenIfTreesWouldDiffer, + ITreeAndVersionSource originalTreeSource, AsyncLazy lazyComputation) : ITreeAndVersionSource { - public readonly ITextAndVersionSource SiblingTextSource = siblingTextSource; - public readonly ITreeAndVersionSource SiblingTreeSource = siblingTreeSource; - public readonly bool ForceEvenIfTreesWouldDiffer = forceEvenIfTreesWouldDiffer; + public readonly ITreeAndVersionSource OriginalTreeSource = originalTreeSource; public Task GetValueAsync(CancellationToken cancellationToken) => lazyComputation.GetValueAsync(cancellationToken); @@ -62,11 +58,10 @@ public DocumentState UpdateTextAndTreeContents( // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is // already linked to some other file, just point directly at that file instead. This can help when there are // pathological cases (like a large solution with a single file linked into every project in the solution). - while (siblingTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource && - linkedFileTreeAndVersionSource.SiblingTextSource == siblingTextSource && - linkedFileTreeAndVersionSource.ForceEvenIfTreesWouldDiffer == forceEvenIfTreesWouldDiffer) + var originalTreeSource = this.TreeSource; + while (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) { - siblingTreeSource = linkedFileTreeAndVersionSource.SiblingTreeSource; + originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; } // Always pass along the sibling text. We will always be in sync with that. @@ -78,14 +73,12 @@ public DocumentState UpdateTextAndTreeContents( // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. var lazyComputation = AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (Attributes.SyntaxTreeFilePath, LanguageServices, LoadTextOptions, ParseOptions, TreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (Attributes.SyntaxTreeFilePath, LanguageServices, LoadTextOptions, ParseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); var newTreeSource = new LinkedFileTreeAndVersionSource( - siblingTextSource, - siblingTreeSource, - forceEvenIfTreesWouldDiffer, + originalTreeSource, lazyComputation); return new DocumentState( diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 61fd430076efe..f75eb77eeb55a 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5110,6 +5110,7 @@ public async Task TestLargeLinkedFileChain() var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); var tree = await document2.GetSyntaxTreeAsync(); + Console.WriteLine(tree); } } } From 68294c12991463fb66dc355dbdc8e7e5446d52f6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:08:52 -0700 Subject: [PATCH 0854/1047] UPdate test --- src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 61fd430076efe..1306270e3e31f 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5088,11 +5088,13 @@ public async Task TestLargeLinkedFileChain() var project1 = workspace.CurrentSolution .AddProject($"Project1", $"Project1", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("DEBUG")) .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; var documentId1 = project1.DocumentIds.Single(); var project2 = project1.Solution .AddProject($"Project2", $"Project2", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("RELEASE")) .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; var documentId2 = project2.DocumentIds.Single(); @@ -5103,13 +5105,17 @@ public async Task TestLargeLinkedFileChain() for (var i = 0; i < 4000; i++) { workspace.SetCurrentSolution( - old => old.WithDocumentText(documentId1, SourceText.From($"//{new string('.', i)}//")), + old => old.WithDocumentText(documentId1, SourceText.From($"#if true //{new string('.', i)}//")), (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); + + // ensure that the first document is fine, and we're not stack overflowing on it. + var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); + await document1.GetSyntaxRootAsync(); } var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); - var tree = await document2.GetSyntaxTreeAsync(); + var root = await document2.GetSyntaxRootAsync(); } } } From 10e6859195926af63b7b1b1d5c8334557e63d468 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:34:22 -0700 Subject: [PATCH 0855/1047] Rename --- .../Core/Portable/Workspace/Solution/DocumentState.cs | 8 ++++---- ...eAndVersionSource.cs => SimpleTreeAndVersionSource.cs} | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/{AsyncLazyTreeAndVersionSource.cs => SimpleTreeAndVersionSource.cs} (82%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 8a4ca25d9b8b0..8e7daaa323d16 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -105,7 +105,7 @@ protected static ITreeAndVersionSource CreateLazyFullyParsedTree( LanguageServices languageServices, PreservationMode mode = PreservationMode.PreserveValue) { - return AsyncLazyTreeAndVersionSource.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => FullyParseTreeAsync(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), static (arg, c) => FullyParseTree(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), arg: (newTextSource, loadTextOptions, filePath, options, languageServices, mode)); @@ -168,7 +168,7 @@ private static ITreeAndVersionSource CreateLazyIncrementallyParsedTree( ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions) { - return AsyncLazyTreeAndVersionSource.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => IncrementallyParseTreeAsync(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), static (arg, c) => IncrementallyParseTree(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), arg: (oldTreeSource, newTextSource, loadTextOptions)); @@ -368,7 +368,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso } if (newTree is not null) - newTreeSource = AsyncLazyTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); + newTreeSource = SimpleTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); } // If we weren't able to reuse in a smart way, just reparse @@ -528,7 +528,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) _options, textSource: text, LoadTextOptions, - treeSource: AsyncLazyTreeAndVersionSource.Create(treeAndVersion)); + treeSource: SimpleTreeAndVersionSource.Create(treeAndVersion)); // use static method so we don't capture references to this static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs similarity index 82% rename from src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs index 6780ff809213c..46be64b1bd3ec 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs @@ -14,11 +14,11 @@ namespace Microsoft.CodeAnalysis; /// Simple implementation of backed by an opaque ."/> /// -internal sealed class AsyncLazyTreeAndVersionSource : ITreeAndVersionSource +internal sealed class SimpleTreeAndVersionSource : ITreeAndVersionSource { private readonly AsyncLazy _source; - private AsyncLazyTreeAndVersionSource(AsyncLazy source) + private SimpleTreeAndVersionSource(AsyncLazy source) { _source = source; } @@ -32,13 +32,13 @@ public TreeAndVersion GetValue(CancellationToken cancellationToken) public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) => _source.TryGetValue(out value); - public static AsyncLazyTreeAndVersionSource Create( + public static SimpleTreeAndVersionSource Create( Func> asynchronousComputeFunction, Func? synchronousComputeFunction, TArg arg) { return new(AsyncLazy.Create(asynchronousComputeFunction, synchronousComputeFunction, arg)); } - public static AsyncLazyTreeAndVersionSource Create(TreeAndVersion source) + public static SimpleTreeAndVersionSource Create(TreeAndVersion source) => new(AsyncLazy.Create(source)); } From edcebb9e15db4612aec2670fb5f9d352b6bed8bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:35:05 -0700 Subject: [PATCH 0856/1047] docs --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index a134e88a53a9a..0d6e378026426 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -16,13 +16,13 @@ internal partial class DocumentState /// /// when we're linked to another file. This allows us to defer to the linked /// file to get the actual root. Note: we won't know if we can actually use the contents of that linked file until - /// we actually go and realize it. And if we are unable to use it, we will do a normal incremental parse on - /// ourselves and the *text contents* of the linked file. + /// we actually go and realize it. And if we are unable to use it, we will do a normal incremental parse on our own + /// tree and the *text contents* of the linked file. /// /// /// This holds onto our original tree source so that if we're continually overwritten (say because our linked file /// keeps getting edited) that we don't form a long chain of links we have to walk in the case where we do *not* - /// reused the contents of hte linked file. In the case where we do not reuse, we will just do a normal incremental + /// reused the contents of the linked file. In the case where we do not reuse, we will just do a normal incremental /// parse and we don't want a chain of those. Instead, we'll have our last tree, and the latest linked-file text /// and can incrementally parse between those two. /// From ee651b24e75db053ecf307179cf3c8cc08345d11 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:37:29 -0700 Subject: [PATCH 0857/1047] Revert --- .../Solution/DocumentState_LinkedFileReuse.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 0d6e378026426..88272a4f0cedc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -106,9 +106,9 @@ private static DocumentState UpdateTextAndTreeContentsWorker( // the tree is never actually needed. var lazyComputation = AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.attributes, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.attributes, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (attributes, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); var newTreeSource = new LinkedFileTreeAndVersionSource( originalTreeSource, @@ -124,7 +124,7 @@ private static DocumentState UpdateTextAndTreeContentsWorker( newTreeSource); static bool TryReuseSiblingRoot( - DocumentInfo.DocumentAttributes attributes, + string filePath, LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, @@ -149,7 +149,7 @@ static bool TryReuseSiblingRoot( // a property of the file, and that should stay the same even if linked into multiple projects. var newTree = treeFactory.CreateSyntaxTree( - attributes.SyntaxTreeFilePath, + filePath, parseOptions, siblingTree.Encoding, loadTextOptions.ChecksumAlgorithm, @@ -222,7 +222,7 @@ bool CanReuseSiblingRoot(bool forceEvenIfTreesWouldDiffer) } static async Task TryReuseSiblingTreeAsync( - DocumentInfo.DocumentAttributes attributes, + string filePath, LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, @@ -237,7 +237,7 @@ static async Task TryReuseSiblingTreeAsync( var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - if (TryReuseSiblingRoot(attributes, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) return newTreeAndVersion; // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. @@ -245,7 +245,7 @@ static async Task TryReuseSiblingTreeAsync( } static TreeAndVersion TryReuseSiblingTree( - DocumentInfo.DocumentAttributes attributes, + string filePath, LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, @@ -260,7 +260,7 @@ static TreeAndVersion TryReuseSiblingTree( var siblingRoot = siblingTree.GetRoot(cancellationToken); - if (TryReuseSiblingRoot(attributes, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) return newTreeAndVersion; // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. From 23af9de4b54cbd8012f38b8945823e25ae081c89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:38:46 -0700 Subject: [PATCH 0858/1047] Simplify --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 88272a4f0cedc..10c4a58007995 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -115,13 +115,7 @@ private static DocumentState UpdateTextAndTreeContentsWorker( lazyComputation); return new DocumentState( - languageServices, - services, - attributes, - parseOptions, - siblingTextSource, - loadTextOptions, - newTreeSource); + languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); static bool TryReuseSiblingRoot( string filePath, From ea3c5ff78f267ba5f526754a5ee1300d0e454cec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:53:27 -0700 Subject: [PATCH 0859/1047] Enhance test --- .../CoreTest/SolutionTests/SolutionTests.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index f66e38960dd89..2157655393329 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5081,8 +5081,12 @@ public async Task TestFrozenPartialSolutionOtherLanguage() Assert.Null(frozenCompilation); } - [Fact] - public async Task TestLargeLinkedFileChain() + [Theory] + [InlineData(1000)] + [InlineData(2000)] + [InlineData(4000)] + [InlineData(8000)] + public async Task TestLargeLinkedFileChain(int intermediatePullCount) { using var workspace = CreateWorkspace(); @@ -5106,24 +5110,31 @@ public async Task TestLargeLinkedFileChain() _ => project2.Solution, (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null)); - var lastContents = ""; - for (var i = 0; i < 4000; i++) + for (var i = 1; i <= 8000; i++) { - lastContents = $"#if true //{new string('.', i)}//"; + var lastContents = $"#if true //{new string('.', i)}//"; workspace.SetCurrentSolution( old => old.WithDocumentText(documentId1, SourceText.From(lastContents)), (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); - // ensure that the first document is fine, and we're not stack overflowing on it. - var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); - await document1.GetSyntaxRootAsync(); - } + // ensure that the first document is fine, and we're not stack overflowing on it. Do this on a disparate + // cadence from our pulls of the second document to ensure we are testing the case where we haven't + // necessarily immediately pulled on hte first doc before pulling on the second. + if (i % 33 == 0) + { + var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); + await document1.GetSyntaxRootAsync(); + } - var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); + if (i % intermediatePullCount == 0) + { + var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); - // Getting the second document should both be fine, and have contents equivalent to what is in the first document. - var root = await document2.GetSyntaxRootAsync(); - Assert.Equal(lastContents, root.ToFullString()); + // Getting the second document should both be fine, and have contents equivalent to what is in the first document. + var root = await document2.GetSyntaxRootAsync(); + Assert.Equal(lastContents, root.ToFullString()); + } + } } } } From d2d0da95f80dc696ec90c80dea8f89b818f39e1b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 14:01:05 -0700 Subject: [PATCH 0860/1047] Look at one link in the chain --- .../Solution/DocumentState_LinkedFileReuse.cs | 15 +++++++++++++-- .../CoreTest/SolutionTests/SolutionTests.cs | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 10c4a58007995..e764b133c6e64 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -70,9 +70,11 @@ public DocumentState UpdateTextAndTreeContents( // We don't want to point at a long chain of linked files, deferring to each next link of the chain to // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is // already linked to some other file, just point directly at that file instead. This can help when there are - // pathological cases (like a large solution with a single file linked into every project in the solution). + // pathological cases (like a large solution with a single file linked into every project in the solution). We + // only need to look one deep here as we'll pull that tree source forward to our level. If another link is + // later added to us, it will do the same thing. var originalTreeSource = this.TreeSource; - while (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) + if (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; // Always pass along the sibling text. We will always be in sync with that. @@ -105,6 +107,7 @@ private static DocumentState UpdateTextAndTreeContentsWorker( // that we don't actually realize the tree until we need it. This way we can avoid doing extra work if // the tree is never actually needed. +#if true var lazyComputation = AsyncLazy.Create( static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), @@ -114,6 +117,14 @@ private static DocumentState UpdateTextAndTreeContentsWorker( originalTreeSource, lazyComputation); +#else + var newTreeSource = SimpleTreeAndVersionSource.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + +#endif + return new DocumentState( languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 2157655393329..ba14298ac442e 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5117,9 +5117,10 @@ public async Task TestLargeLinkedFileChain(int intermediatePullCount) old => old.WithDocumentText(documentId1, SourceText.From(lastContents)), (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); - // ensure that the first document is fine, and we're not stack overflowing on it. Do this on a disparate - // cadence from our pulls of the second document to ensure we are testing the case where we haven't - // necessarily immediately pulled on hte first doc before pulling on the second. + // Ensure that the first document is fine, and we're not stack overflowing on simply getting the tree + // from it. Do this on a disparate cadence from our pulls of the second document to ensure we are + // testing the case where we haven't necessarily immediately pulled on hte first doc before pulling on + // the second. if (i % 33 == 0) { var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); From 7b33c3d8a1b54a0962e17b05ae8a5d25a28576c5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 14:12:15 -0700 Subject: [PATCH 0861/1047] Delete --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index e764b133c6e64..f553580c59f03 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -107,7 +107,6 @@ private static DocumentState UpdateTextAndTreeContentsWorker( // that we don't actually realize the tree until we need it. This way we can avoid doing extra work if // the tree is never actually needed. -#if true var lazyComputation = AsyncLazy.Create( static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), @@ -117,14 +116,6 @@ private static DocumentState UpdateTextAndTreeContentsWorker( originalTreeSource, lazyComputation); -#else - var newTreeSource = SimpleTreeAndVersionSource.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - -#endif - return new DocumentState( languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); From c943a140f294139757820ae4b4ca7ce60cc4953d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 14:49:12 -0700 Subject: [PATCH 0862/1047] Docs --- .../Solution/DocumentState_LinkedFileReuse.cs | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index f553580c59f03..8c481c1b4a3be 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -14,19 +14,13 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { /// - /// when we're linked to another file. This allows us to defer to the linked - /// file to get the actual root. Note: we won't know if we can actually use the contents of that linked file until - /// we actually go and realize it. And if we are unable to use it, we will do a normal incremental parse on our own - /// tree and the *text contents* of the linked file. + /// when we're linked to another file (a 'sibling') and will attempt to reuse + /// that sibling's tree as our own. Note: we won't know if we can actually use the contents of that sibling file + /// until we actually go and realize it, as it may contains constructs (like pp-directives) that prevent use. In + /// that case, we'll fall back to a normal incremental parse between our original and the latest text contents of our sibling's file. /// - /// - /// This holds onto our original tree source so that if we're continually overwritten (say because our linked file - /// keeps getting edited) that we don't form a long chain of links we have to walk in the case where we do *not* - /// reused the contents of the linked file. In the case where we do not reuse, we will just do a normal incremental - /// parse and we don't want a chain of those. Instead, we'll have our last tree, and the latest linked-file text - /// and can incrementally parse between those two. - /// - private sealed class LinkedFileTreeAndVersionSource( + private sealed class LinkedFileReuseTreeAndVersionSource( ITreeAndVersionSource originalTreeSource, AsyncLazy lazyComputation) : ITreeAndVersionSource { @@ -67,14 +61,15 @@ public DocumentState UpdateTextAndTreeContents( Contract.ThrowIfNull(siblingTreeSource); - // We don't want to point at a long chain of linked files, deferring to each next link of the chain to - // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is - // already linked to some other file, just point directly at that file instead. This can help when there are - // pathological cases (like a large solution with a single file linked into every project in the solution). We - // only need to look one deep here as we'll pull that tree source forward to our level. If another link is + // We don't want to point at a long chain of transformations as our sibling files change, deferring to each next + // link of the chain to potentially do the work (or potentially failing out). So, if we're about to do this, + // instead return our original tree-source so that in the case we are unable to use the sibling file's root, we + // can do a single step incremental parse between our original tree and the final sibling text. + // + // We only need to look one deep here as we'll pull that tree source forward to our level. If another link is // later added to us, it will do the same thing. var originalTreeSource = this.TreeSource; - if (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) + if (originalTreeSource is LinkedFileReuseTreeAndVersionSource linkedFileTreeAndVersionSource) originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; // Always pass along the sibling text. We will always be in sync with that. @@ -97,24 +92,18 @@ private static DocumentState UpdateTextAndTreeContentsWorker( ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer) { - // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as - // much memory as possible with linked files. However, we can't point at that source directly. If we did, - // we'd produce the *exact* same tree-reference as another file. That would be bad as it would break the - // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers - // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. - - // We're going to defer to the sibling tree source to get the tree if possible. We'll do this lazily so - // that we don't actually realize the tree until we need it. This way we can avoid doing extra work if - // the tree is never actually needed. + // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as much + // memory as possible with linked files. However, we can't point at that source directly. If we did, we'd + // produce the *exact* same tree-reference as another file. That would be bad as it would break the invariant + // that each document gets a unique SyntaxTree. So, instead, we produce a tree-source that defers to the + // provided source, gets the tree from it, and then wraps its root in a new tree for us. var lazyComputation = AsyncLazy.Create( static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - var newTreeSource = new LinkedFileTreeAndVersionSource( - originalTreeSource, - lazyComputation); + var newTreeSource = new LinkedFileReuseTreeAndVersionSource(originalTreeSource, lazyComputation); return new DocumentState( languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); From c67de828b24801f16d3704360c73e073b5eb045d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 25 Apr 2024 16:02:11 -0700 Subject: [PATCH 0863/1047] Reset session id in EndSession (#73219) --- .../Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index d0762770efc95..31a5509f51e6f 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -121,6 +121,7 @@ public void EndSession() { Contract.ThrowIfFalse(_sessionId != default, "Session has not started"); _encService.EndDebuggingSession(_sessionId); + _sessionId = default; } internal TestAccessor GetTestAccessor() From aebc3e9ffb7bdd20a859608658a9c291b1383b63 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 16:26:29 -0700 Subject: [PATCH 0864/1047] Fix issue with temp storage system on unix systems --- .../TemporaryStorageService.Factory.cs | 8 +--- ...emporaryStorageService.MemoryMappedInfo.cs | 12 +++--- .../TemporaryStorageService.cs | 13 +++++- .../ITemporaryStorageService.cs | 8 +++- .../TemporaryStorageIdentifier.cs | 5 +-- ...orageService.TrivialStorageStreamHandle.cs | 26 ------------ ...StorageService.TrivialStorageTextHandle.cs | 26 ------------ .../TrivialTemporaryStorageService.cs | 42 ------------------- .../CoreTest/SolutionTests/SolutionTests.cs | 17 +------- 9 files changed, 28 insertions(+), 129 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs 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 7f6b5d80dc9e2..0e0c87e785c27 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -24,7 +24,7 @@ internal partial class TemporaryStorageService /// This class and its nested types have familiar APIs and predictable behavior when used in other code, but /// are non-trivial to work on. /// - internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string? name, long offset, long size) { /// /// The memory mapped file. @@ -45,16 +45,14 @@ internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string /// private ReferenceCountedDisposable.WeakReference _weakReadAccessor; - public static MemoryMappedInfo OpenExisting(string name, long offset, long size) - => new(MemoryMappedFile.OpenExisting(name), name, offset, size); - - public static MemoryMappedInfo CreateNew(string name, long size) + 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 diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index 7a8431f550016..079e12d3f7edf 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -173,6 +173,7 @@ MemoryMappedInfo WriteToMemoryMappedFile() 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); } @@ -183,6 +184,7 @@ internal TemporaryStorageTextHandle GetTextHandle( 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); } @@ -229,8 +231,15 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) } } - public static string CreateUniqueName(long size) - => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); + 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 can return the TrivialTemporaryStorageService + // until https://github.com/dotnet/runtime/issues/30878 is fixed. + return PlatformInformation.IsWindows || PlatformInformation.IsRunningOnMono + ? $"Roslyn Shared File: Size={size} Id={Guid.NewGuid():N}" + : null; + } public sealed class TemporaryStorageTextHandle( TemporaryStorageService storageService, diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 4079f19f50248..c19c7b99a852b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -18,7 +18,13 @@ public interface ITemporaryStorageService : IWorkspaceService } /// -/// API to allow a client to write data to memory-mapped-file storage (allowing it to be shared across processes). +/// 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 { diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs index 2457297fca662..67f19bc52fdce 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.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.Runtime.Serialization; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -12,11 +11,11 @@ namespace Microsoft.CodeAnalysis.Host; /// used to identify that segment across processes, allowing for efficient sharing of data. /// internal sealed record TemporaryStorageIdentifier( - string Name, long Offset, long Size) + string? Name, long Offset, long Size) { public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) => new( - reader.ReadRequiredString(), + reader.ReadString(), reader.ReadInt64(), reader.ReadInt64()); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs deleted file mode 100644 index d3cb02f201d39..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.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.IO; -using System.Threading; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis; - -internal sealed partial class TrivialTemporaryStorageService -{ - private sealed class TrivialStorageStreamHandle( - TemporaryStorageIdentifier storageIdentifier, - MemoryStream streamCopy) : ITemporaryStorageStreamHandle - { - public TemporaryStorageIdentifier Identifier => storageIdentifier; - - public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) - { - // Return a read-only view of the underlying buffer to prevent users from overwriting or directly - // disposing the backing storage. - return new MemoryStream(streamCopy.GetBuffer(), 0, (int)streamCopy.Length, writable: false); - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs deleted file mode 100644 index fb09daa83f991..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.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.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis; - -internal sealed partial class TrivialTemporaryStorageService -{ - private sealed class TrivialStorageTextHandle( - TemporaryStorageIdentifier identifier, - SourceText sourceText) : ITemporaryStorageTextHandle - { - public TemporaryStorageIdentifier Identifier => identifier; - - public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) - => sourceText; - - public Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) - => Task.FromResult(ReadFromTemporaryStorage(cancellationToken)); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs deleted file mode 100644 index a9304b70e07a6..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.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.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis; - -internal sealed partial class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal -{ - public static readonly TrivialTemporaryStorageService Instance = new(); - - private TrivialTemporaryStorageService() - { - } - - public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) - { - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); - var handle = new TrivialStorageTextHandle(identifier, text); - return handle; - } - - public Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) - => Task.FromResult(WriteToTemporaryStorage(text, cancellationToken)); - - public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) - { - var newStream = new MemoryStream(); - stream.CopyTo(newStream); - newStream.Position = 0; - - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TrivialStorageStreamHandle(identifier, newStream); - return handle; - } -} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 67e9cabedec10..f755dc5c00bd6 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2639,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 {}"; @@ -2669,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; From d358b004fbb6594b1fc4ad3bc756c03399db022e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 17:06:01 -0700 Subject: [PATCH 0865/1047] remove assert --- .../Core/Portable/TemporaryStorage/TemporaryStorageService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index 079e12d3f7edf..8c9fe3c9ac04e 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -224,7 +224,6 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) else { // Reserve additional space in the existing storage location - Contract.ThrowIfNull(_name); _offset += size; return new MemoryMappedInfo(reference, _name, _offset - size, size); } From 7825bd706c9aa3b4544a85f072453d32eb9881ae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 17:07:32 -0700 Subject: [PATCH 0866/1047] Docs --- .../Host/TemporaryStorage/TemporaryStorageIdentifier.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs index 67f19bc52fdce..8ac99018bef6b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs @@ -7,9 +7,11 @@ namespace Microsoft.CodeAnalysis.Host; /// -/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be -/// used to identify that segment across processes, allowing for efficient sharing of data. +/// 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) { From a0375ce47970b6b2f4dcded65ac25ae8d0155877 Mon Sep 17 00:00:00 2001 From: Bret Johnson Date: Thu, 25 Apr 2024 20:09:23 -0400 Subject: [PATCH 0867/1047] Allow use of more Hot Reload brokered services by LSP (for VS Code) Add the HotReloadOptionService and HotReloadLoggerService to the list of remote brokered services that are registered, allowing use of those services by MAUI Hot Reload in VS Code. Both of these Hot Reload services are proffered by vs-green debugger functionality, implemented in TypeScript there. --- .../BrokeredServices/Services/Descriptors.cs | 4 +++- .../Remote/Core/BrokeredServiceDescriptors.cs | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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 dd0618831e79c..4fa57508b1b38 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/BrokeredServices/Services/Descriptors.cs @@ -45,7 +45,9 @@ internal class Descriptors { 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.MauiLaunchCustomizerServiceDescriptor.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/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs b/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs index 100a69202bfb6..d66c72ff84200 100644 --- a/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/BrokeredServiceDescriptors.cs @@ -74,7 +74,8 @@ protected override JsonRpcConnection CreateConnection(JsonRpc jsonRpc) 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 MauiLaunchCustomizerServiceDescriptor = CreateMauiServiceDescriptor("MauiLaunchCustomizerService", 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); @@ -98,6 +99,13 @@ public static ServiceJsonRpcDescriptor CreateServerServiceDescriptor(string serv 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). /// From 90311b3253eb8b2b43ac448d9331ffdccc39b624 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 17:23:43 -0700 Subject: [PATCH 0868/1047] Docs --- .../Portable/TemporaryStorage/TemporaryStorageService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index 8c9fe3c9ac04e..56a205c39481b 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -232,9 +232,9 @@ private MemoryMappedInfo CreateTemporaryStorage(long 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 can return the TrivialTemporaryStorageService - // until https://github.com/dotnet/runtime/issues/30878 is fixed. + // 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; From 59a7bacab3df80c68d4adee9a7e116e8343b3fca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 18:14:06 -0700 Subject: [PATCH 0869/1047] Change API now that we know it's always about MMFS --- .../TemporaryStorageService.TemporaryStorageStreamHandle.cs | 3 --- .../Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs index 7b06db02ddbff..52d97d1bd762c 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -18,9 +18,6 @@ public sealed class TemporaryStorageStreamHandle( { public TemporaryStorageIdentifier Identifier => identifier; - Stream ITemporaryStorageStreamHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) - => ReadFromTemporaryStorage(cancellationToken); - public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 418a8a7fe50cf..a278f5a73ab30 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -22,5 +22,5 @@ internal interface ITemporaryStorageStreamHandle /// Reads the data indicated to by this handle into a stream. This stream can be created in a different process /// than the one that wrote the data originally. /// - Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); + UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken); } From de4d586771ebfa0b1eb67a74d818365912540674 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Fri, 26 Apr 2024 11:10:09 -0700 Subject: [PATCH 0870/1047] Non-array params collections are not applicable in expanded form in C# 12 and below. (#73223) Related to https://github.com/dotnet/csharplang/issues/8061 Corresponding spec change - https://github.com/dotnet/csharplang/pull/8077 --- .../Portable/Binder/Binder_Conversions.cs | 4 - .../Portable/Binder/Binder_Expressions.cs | 9 +- .../Portable/Binder/Binder_Invocation.cs | 10 +- .../OverloadResolution/OverloadResolution.cs | 4 +- .../AnonymousTypes/AnonymousTypeManager.cs | 4 +- .../AnonymousType.DelegatePublicSymbol.cs | 15 +- .../Emit2/Semantics/ParamsCollectionTests.cs | 761 ++++++++++++++---- 7 files changed, 601 insertions(+), 206 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 50ac6b57f6cdb..3c0a19894139f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -2021,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(); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index d99058f28b208..dd1e7e83bc1e8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -6608,6 +6608,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, @@ -9732,8 +9733,8 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( argumentSyntax, singleCandidate); } } - // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. - else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(finalApplicableCandidates[0])) + // For C# 12 and earlier always bind at runtime. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12) { var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); resultWithSingleCandidate.ResultsBuilder.Add(finalApplicableCandidates[0]); @@ -9744,6 +9745,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( } if (finalApplicableCandidates.Length != 1 && + Compilation.LanguageVersion > LanguageVersion.CSharp12 && // The following check (while correct) is redundant otherwise HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(analyzedArguments.Arguments, finalApplicableCandidates)) { Error(diagnostics, @@ -10785,8 +10787,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_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 6e79c7c3f653c..839d23ee94800 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -662,8 +662,8 @@ private BoundExpression BindDelegateInvocation( result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } - // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. - else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(applicable)) + // 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); } @@ -878,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, @@ -1011,10 +1012,9 @@ private BoundExpression TryEarlyBindSingleCandidateInvocationWithDynamicArgument return null; } - // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for local functions or expanded non-array params cases. + // 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 || - IsMemberWithExpandedNonArrayParamsCollection(methodResolutionResult)) + singleCandidate.MethodKind == MethodKind.LocalFunction) { var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index d70eed323fcdc..385db1686b016 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -1171,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 _); } 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/Test/Emit2/Semantics/ParamsCollectionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs index 83bd9f2da8660..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() { @@ -5852,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() @@ -5862,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("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.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("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() @@ -5909,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), @@ -5951,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] @@ -6015,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) + 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), + // (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 = """ @@ -6067,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), + // (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) + 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 = """ @@ -6650,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 @@ -6667,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] @@ -6743,18 +7007,12 @@ class C2 [Fact] public void DynamicInvocation_Indexer_02_AmbiguousDynamicParamsArgument() { - var src = """ + var src1 = """ using System.Collections.Generic; -class Program +public class Program { - static void Main() - { - dynamic d = 1; - _ = new Program()[d]; - } - - int this[params IEnumerable b] + public int this[params IEnumerable b] { get { @@ -6764,21 +7022,70 @@ int this[params IEnumerable b] } } """; - var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + 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); + + 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( - // (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. + // (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(8, 27) + 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 src = """ + 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() @@ -6804,58 +7111,86 @@ static void Main() _ = 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); + var comp = CreateCompilation(src2, references: [comp1Ref], targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.ReleaseExe); - 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. + 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(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. + 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(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. + 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(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. + 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(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. + 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(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. + 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(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. + 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(26, 13) + 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( + // (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) ); + + 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] @@ -7278,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() @@ -7288,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( - // (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.RegularNext); + + 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.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() @@ -7338,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] From 192c83a3ea905674526a4a9761505dc6732c1984 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 26 Apr 2024 11:51:24 -0700 Subject: [PATCH 0871/1047] Fix regressions in MEF-based pull diagnostics --- .../AlwaysActivateInProcLanguageClient.cs | 4 ++-- .../Protocol/DefaultCapabilitiesProvider.cs | 10 ---------- .../DiagnosticSourceManager.cs | 20 +++++++++++-------- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 3 +++ .../IDiagnosticSourceManager.cs | 5 +++-- .../IDiagnosticSourceProvider.cs | 3 +++ ...mentsAndProjectDiagnosticSourceProvider.cs | 3 +++ ...ocumentNonLocalDiagnosticSourceProvider.cs | 3 +++ ...ntPullDiagnosticsHandler_IOnInitialized.cs | 8 +++++--- ...cePullDiagnosticsHandler_IOnInitialized.cs | 4 ++-- ...EditAndContinueDiagnosticSourceProvider.cs | 3 +++ ...EditAndContinueDiagnosticSourceProvider.cs | 3 +++ .../DocumentTaskDiagnosticSourceProvider.cs | 3 +++ .../WorkspaceTaskDiagnosticSourceProvider.cs | 3 +++ .../HotReloadDiagnosticSourceProvider.cs | 3 +++ .../InternalAPI.Unshipped.txt | 1 + .../Internal/XamlDiagnosticSourceProvider.cs | 3 +++ 17 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index dacbc7633a275..b884102f56884 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -74,8 +74,8 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.DiagnosticProvider ??= new(); // VS does not distinguish between document and workspace diagnostics, so we need to merge them. - var diagnosticSourceNames = _diagnosticSourceManager.GetDocumentSourceProviderNames() - .Concat(_diagnosticSourceManager.GetWorkspaceSourceProviderNames()) + var diagnosticSourceNames = _diagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities) + .Concat(_diagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities)) .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 8eb7e16616167..406ea9a4c2de0 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -120,16 +120,6 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) // Using VS server capabilities because we have our own custom client. capabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions { TriggerCharacters = ["'", "/", "\n"] }; - if (!supportsVsExtensions) - { - capabilities.DiagnosticOptions = new DiagnosticOptions - { - InterFileDependencies = true, - WorkDoneProgress = true, - WorkspaceDiagnostics = true, - }; - } - return capabilities; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs index 1b870ac2dc45f..2af9fa8710706 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -45,11 +45,11 @@ public DiagnosticSourceManager([ImportMany] IEnumerable kvp.Name, kvp => kvp); } - public ImmutableArray GetDocumentSourceProviderNames() - => _nameToDocumentProviderMap.Keys.ToImmutableArray(); + public ImmutableArray GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities) + => _nameToDocumentProviderMap.Where(kvp => kvp.Value.IsEnabled(clientCapabilities)).SelectAsArray(kvp => kvp.Key); - public ImmutableArray GetWorkspaceSourceProviderNames() - => _nameToWorkspaceProviderMap.Keys.ToImmutableArray(); + 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); @@ -69,7 +69,10 @@ private static async ValueTask> CreateDiagnost // 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 []; } @@ -79,12 +82,13 @@ private static async ValueTask> CreateDiagnost using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); foreach (var (name, provider) in nameToProviderMap) { - // Exclude Task diagnostics from the aggregated sources. - if (name != PullDiagnosticCategories.Task) + if (!provider.IsEnabled(context.GetRequiredClientCapabilities())) { - var namedSources = await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); - sourcesBuilder.AddRange(namedSources); + continue; } + + var namedSources = await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + sourcesBuilder.AddRange(namedSources); } var sources = sourcesBuilder.ToImmutableAndClear(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index 2f0f50ec3f604..85fca7423bdd3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -19,6 +20,8 @@ internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvide 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) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs index 51e0ef8ff0e10..25fc19acaca03 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; @@ -17,12 +18,12 @@ internal interface IDiagnosticSourceManager /// /// Returns the names of document level s. /// - ImmutableArray GetDocumentSourceProviderNames(); + ImmutableArray GetDocumentSourceProviderNames(ClientCapabilities clientCapabilities); /// /// Returns the names of workspace level s. /// - ImmutableArray GetWorkspaceSourceProviderNames(); + ImmutableArray GetWorkspaceSourceProviderNames(ClientCapabilities clientCapabilities); /// /// Creates document diagnostic sources for the given . diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs index daeb1b4d71aee..773f3a309c94e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -23,6 +24,8 @@ internal interface IDiagnosticSourceProvider /// string Name { get; } + bool IsEnabled(ClientCapabilities clientCapabilities); + /// /// Creates the diagnostic sources. /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 9daa0cf91dd5c..58cca636c91f5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -13,6 +13,7 @@ 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; @@ -28,6 +29,8 @@ internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( public bool IsDocument => false; public string Name => PullDiagnosticCategories.WorkspaceDocumentsAndProject; + public bool IsEnabled(ClientCapabilities clientCapabilities) => true; + /// /// There are three potential sources for reporting workspace diagnostics: /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 9b8f9116ebc19..5ac98fefc7389 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -26,6 +27,8 @@ internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( 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. 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 a8332219d1d12..9720adda62bfd 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -24,7 +24,7 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // to dynamically register/unregister the non-local document diagnostic source. // Task diagnostics shouldn't be reported through VSCode (it has its own task stuff). Additional cleanup needed. - var sources = DiagnosticSourceManager.GetDocumentSourceProviderNames().Where(source => source != PullDiagnosticCategories.Task); + var sources = DiagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities); var registrations = sources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @@ -36,11 +36,13 @@ await _clientLanguageServerManager.SendRequestAsync( } Registration FromSourceName(string sourceName) - => new() + { + return new() { Id = Guid.NewGuid().ToString(), Method = Methods.TextDocumentDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkspaceDiagnostics = false } }; + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 2e34f2dbccf03..44c880b7680e2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -15,7 +15,7 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { - var providerNames = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(); + var providerNames = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams @@ -26,7 +26,7 @@ await _clientLanguageServerManager.SendRequestAsync( // we need to use textDocument/diagnostic instead of workspace/diagnostic Method = Methods.TextDocumentDiagnosticName, Id = name, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = name, InterFileDependencies = true, WorkDoneProgress = true } + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = name, InterFileDependencies = true, WorkDoneProgress = true, WorkspaceDiagnostics = true } }).ToArray() }, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs index 63e5866c67b1e..5f66ca41cdddb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -20,6 +21,8 @@ internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() : IDiagn 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) diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs index 847a909a2ff25..af92108b86cae 100644 --- a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -9,6 +9,7 @@ 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; @@ -21,6 +22,8 @@ internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : IDiag 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); diff --git a/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs index 6b63bdf91f220..5e78b219c22f3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; @@ -20,6 +21,8 @@ internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptio 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) diff --git a/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs index 53b1bf7e03cd5..2a273d85d398a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs @@ -11,6 +11,7 @@ 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; @@ -23,6 +24,8 @@ internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOpti 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); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs index 495e586ea6f13..88bd5cceb9f90 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -12,6 +12,7 @@ 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; @@ -20,6 +21,8 @@ internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa 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) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 019251a6dad44..a831db9ed8865 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -39,6 +39,7 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagno 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 diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs index 110413bfdb9ef..526a26f5e57e8 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs @@ -10,6 +10,7 @@ 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; @@ -22,6 +23,8 @@ internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = 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 && From 6450bdc77bed2a5422d87d2992352dbe47d9052b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 13:07:28 -0700 Subject: [PATCH 0872/1047] Remove project --- .../NavigateToItemProvider.Callback.cs | 29 ++++++++----------- .../VSTypeScriptNavigateToSearchService.cs | 2 ++ .../NavigateTo/INavigateToSearchCallback.cs | 2 +- .../NavigateTo/INavigateToSearchResult.cs | 3 +- .../Portable/NavigateTo/NavigateToSearcher.cs | 4 +-- .../NavigateTo/RoslynNavigateToItem.cs | 5 +++- .../Symbols/WorkspaceSymbolsHandler.cs | 9 ++++-- .../RoslynNavigateToSearchCallback.cs | 4 +-- .../ProgressionNavigateToSearchCallback.cs | 8 +++-- 9 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index 7c1ff6aa7b44d..ea5b00db9a55b 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs @@ -39,22 +39,7 @@ public void Done(bool isFullyLoaded) } } - public Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) - { - ReportMatchResult(project, result); - return Task.CompletedTask; - } - - public void ReportProgress(int current, int maximum) - { - _callback.ReportProgress(current, maximum); - } - - public void ReportIncomplete() - { - } - - private void ReportMatchResult(Project project, INavigateToSearchResult result) + public Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); @@ -67,7 +52,7 @@ private void ReportMatchResult(Project project, INavigateToSearchResult result) var navigateToItem = new NavigateToItem( result.Name, result.Kind, - GetNavigateToLanguage(project.Language), + GetNavigateToLanguage(result.Language), result.SecondarySort, result, patternMatch, @@ -84,6 +69,16 @@ private void ReportMatchResult(Project project, INavigateToSearchResult result) // 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; + } + + public void ReportProgress(int current, int maximum) + { + _callback.ReportProgress(current, maximum); + } + + public void ReportIncomplete() + { } private static PatternMatchKind GetPatternMatchKind(NavigateToMatchKind matchKind) diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs index 55600bdde6add..88f322eb8e6bc 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs @@ -128,5 +128,7 @@ public NavigateToMatchKind MatchKind public INavigableItem NavigableItem => new VSTypeScriptNavigableItemWrapper(_result.NavigableItem); public ImmutableArray Matches => NavigateToSearchResultHelpers.GetMatches(this); + + public string Language => InternalLanguageNames.TypeScript; } } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs index 281c9ffc89b56..1a953b5fc6dfb 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs @@ -12,7 +12,7 @@ internal interface INavigateToSearchCallback void Done(bool isFullyLoaded); void ReportIncomplete(); - Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken); + Task AddItemAsync(INavigateToSearchResult result, 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..4edb9d5034c3a 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; @@ -22,6 +20,7 @@ internal interface INavigateToSearchResult ImmutableArray NameMatchSpans { get; } string SecondarySort { get; } string? Summary { get; } + string Language { get; } INavigableItem NavigableItem { get; } ImmutableArray Matches { get; } diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index a11f4ff35d6f6..58ab0c5a943a2 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -191,7 +191,7 @@ 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.AddItemAsync(r, cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -393,7 +393,7 @@ await processProjectAsync( return Task.CompletedTask; } - return _callback.AddItemAsync(project, result, cancellationToken); + return _callback.AddItemAsync(result, cancellationToken); }, () => this.ProgressItemsCompletedAsync(count: 1, cancellationToken)).ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 7437657dc3fc5..e4e94cdb705d0 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 = ['.']; @@ -118,8 +118,11 @@ public NavigateToSearchResult( _additionalInformation = ComputeAdditionalInformation(in item, itemDocument); _secondarySort = new Lazy(ComputeSecondarySort); + Language = itemDocument.Project.Language; } + public string Language { get; } + private static string ComputeAdditionalInformation(in RoslynNavigateToItem item, Document itemDocument) { // For partial types, state what file they're in so the user can disambiguate the results. diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index 852a0d777a22a..3e9b01ebea765 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -70,16 +70,19 @@ private sealed class LSPNavigateToCallback( BufferedProgress progress) : INavigateToSearchCallback { - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(context.Solution); + + var solution = context.Solution; + 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 service = project.Solution.Services.GetRequiredService(); + var service = solution.Services.GetRequiredService(); var symbolInfo = service.Create( result.Name, result.AdditionalInformation, ProtocolConversions.NavigateToKindToSymbolKind(result.Kind), location, result.NavigableItem.Glyph); diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs index 1c51b4865aa4b..b1458f7836aa3 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs @@ -51,7 +51,7 @@ public void ReportIncomplete() _searchCallback.ReportIncomplete(IncompleteReason.Parsing); } - public Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { // Convert roslyn pattern matches to the platform type. var matches = result.Matches.SelectAsArray(static m => new PatternMatch( @@ -74,7 +74,7 @@ public Task AddItemAsync(Project project, INavigateToSearchResult result, Cancel matches, result.NavigableItem.Document.FilePath, perProviderItemPriority, - project.Language)); + result.Language)); return Task.CompletedTask; } diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs index 5ad659283582a..9402ca83293c9 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs @@ -14,11 +14,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,9 +38,9 @@ public void ReportIncomplete() { } - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { - var node = await _graphBuilder.CreateNodeAsync(project.Solution, result, cancellationToken).ConfigureAwait(false); + 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. From 1c7bbbd0033982ef369e3db2f89d6a4d5d85e4bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 13:11:03 -0700 Subject: [PATCH 0873/1047] in progress --- .../VSTypeScript/VSTypeScriptNavigateToSearchService.cs | 4 ++-- ...stractNavigateToSearchService.CachedDocumentSearch.cs | 2 +- ...actNavigateToSearchService.GeneratedDocumentSearch.cs | 2 +- .../AbstractNavigateToSearchService.NormalSearch.cs | 4 ++-- .../NavigateTo/AbstractNavigateToSearchService.cs | 9 ++------- .../Core/Portable/NavigateTo/INavigateToSearchService.cs | 6 +++--- .../Core/Portable/NavigateTo/NavigateToSearcher.cs | 6 +++--- 7 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs index 88f322eb8e6bc..a14da24e06e52 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs @@ -53,7 +53,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -79,7 +79,7 @@ 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); + await onResultFound(Convert(result)).ConfigureAwait(false); } await onProjectCompleted().ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 41ba1ca6bbe81..63a41e9cbbab8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -63,7 +63,7 @@ public async Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index ee18144bee5f8..9e0d9c9304724 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -21,7 +21,7 @@ public async Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index d434504af767c..a954cc94f2435 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -25,7 +25,7 @@ public async Task SearchDocumentAsync( CancellationToken cancellationToken) { var solution = document.Project.Solution; - var onItemFound = GetOnItemFoundCallback(solution, activeDocument: null, (_, i) => onResultFound(i), cancellationToken); + var onItemFound = GetOnItemFoundCallback(solution, activeDocument: null, onResultFound, cancellationToken); var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) @@ -58,7 +58,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index b9015db743520..2feb9aadf401d 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -34,18 +34,13 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig public bool CanFilter => true; private static Func GetOnItemFoundCallback( - Solution solution, Document? activeDocument, Func onResultFound, CancellationToken cancellationToken) + Solution solution, Document? activeDocument, Func onResultFound, CancellationToken cancellationToken) { return async item => { - // 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); + await onResultFound(result).ConfigureAwait(false); }; } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs index 70365273bff52..f798c1b136bb5 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs @@ -39,7 +39,7 @@ Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken); } @@ -66,7 +66,7 @@ Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken); @@ -84,7 +84,7 @@ Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index 58ab0c5a943a2..c8c64c67a639a 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -346,7 +346,7 @@ private async Task ProcessOrderedProjectsAsync( bool parallel, ImmutableArray> orderedProjects, HashSet seenItems, - Func, Func, Func, Task> processProjectAsync, + Func, Func, Func, Task> processProjectAsync, CancellationToken cancellationToken) { // Process each group one at a time. However, in each group process all projects in parallel to get results @@ -382,7 +382,7 @@ async Task SearchCoreAsync(IGrouping grouping await processProjectAsync( searchService, [.. grouping], - (project, result) => + result => { // 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 @@ -522,7 +522,7 @@ public bool CanFilter public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func onResultFound, 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { foreach (var _ in projects) await onProjectCompleted().ConfigureAwait(false); From a2594705178ef931876f9c3e1abe77d243e9d2aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 13:19:25 -0700 Subject: [PATCH 0874/1047] Project removed --- .../NavigateTo/NavigateToSearcherTests.cs | 22 +++++++++---------- .../NavigateToItemProvider.Callback.cs | 8 +++++-- .../NavigateTo/NavigateToItemProvider.cs | 5 +++-- .../VSTypeScriptNavigateToSearchService.cs | 2 -- .../NavigateTo/INavigateToSearchResult.cs | 1 - .../NavigateTo/RoslynNavigateToItem.cs | 3 --- .../FSharpNavigateToSearchService.cs | 5 ++--- .../OmniSharpNavigateToSearchService.cs | 8 ++++--- .../IdeCoreBenchmarks/NavigateToBenchmarks.cs | 2 +- .../RoslynNavigateToSearchCallback.cs | 7 +++++- .../Def/NavigateTo/RoslynSearchItemsSource.cs | 5 +++-- .../GraphQueries/SearchGraphQuery.cs | 2 +- 12 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index fca8d0d529356..eedd1595855db 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -42,7 +42,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -65,7 +65,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -92,7 +92,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -134,7 +134,7 @@ 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.AddItemAsync(result, 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)); @@ -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.AddItemAsync(result, It.IsAny())) .Returns(Task.CompletedTask); // Because the remote host wasn't fully loaded, we still notify that our results may be incomplete. @@ -247,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.AddItemAsync(result, It.IsAny())) .Returns(Task.CompletedTask); // Because we did a full search, we should let the user know it was totally accurate. @@ -280,7 +280,7 @@ 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.AddItemAsync(result, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); @@ -338,7 +338,7 @@ public class D 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.AddItemAsync(result, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); @@ -365,7 +365,7 @@ private sealed class MockAdvancedNavigateToSearchService : IAdvancedNavigateToSe 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public Task SearchCachedDocumentsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { OnSearchCachedDocumentsAsyncCalled?.Invoke(); return Task.CompletedTask; @@ -377,13 +377,13 @@ public Task SearchDocumentAsync(Document document, string searchPattern, IImmuta return Task.CompletedTask; } - public Task SearchGeneratedDocumentsAsync(Solution solution, ImmutableArray projects, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public Task SearchGeneratedDocumentsAsync(Solution solution, ImmutableArray projects, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + public Task SearchProjectsAsync(Solution solution, ImmutableArray projects, ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, Document? activeDocument, Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { OnSearchProjectsAsyncCalled?.Invoke(); return Task.CompletedTask; diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index ea5b00db9a55b..388a1b1f4da63 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs @@ -7,6 +7,7 @@ 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 +19,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; } @@ -49,10 +52,11 @@ public Task AddItemAsync(INavigateToSearchResult result, CancellationToken cance result.IsCaseSensitive, matchedSpans); + var project = _solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); var navigateToItem = new NavigateToItem( result.Name, result.Kind, - GetNavigateToLanguage(result.Language), + GetNavigateToLanguage(project.Language), result.SecondarySort, result, patternMatch, diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs index f648c168d021a..693198634baee 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.cs @@ -109,9 +109,10 @@ private void StartSearch(INavigateToCallback callback, string searchValue, IImmu ? NavigateToSearchScope.Document : NavigateToSearchScope.Solution; - var roslynCallback = new NavigateToItemProviderCallback(_displayFactory, callback); + var solution = _workspace.CurrentSolution; + var roslynCallback = new NavigateToItemProviderCallback(solution, _displayFactory, callback); var searcher = NavigateToSearcher.Create( - _workspace.CurrentSolution, + solution, _asyncListener, roslynCallback, searchValue, diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs index a14da24e06e52..849b5248a1656 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs @@ -128,7 +128,5 @@ public NavigateToMatchKind MatchKind public INavigableItem NavigableItem => new VSTypeScriptNavigableItemWrapper(_result.NavigableItem); public ImmutableArray Matches => NavigateToSearchResultHelpers.GetMatches(this); - - public string Language => InternalLanguageNames.TypeScript; } } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs index 4edb9d5034c3a..ded2ae3bca7f4 100644 --- a/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs +++ b/src/Features/Core/Portable/NavigateTo/INavigateToSearchResult.cs @@ -20,7 +20,6 @@ internal interface INavigateToSearchResult ImmutableArray NameMatchSpans { get; } string SecondarySort { get; } string? Summary { get; } - string Language { get; } INavigableItem NavigableItem { get; } ImmutableArray Matches { get; } diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index e4e94cdb705d0..325b107a6cf29 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -118,11 +118,8 @@ public NavigateToSearchResult( _additionalInformation = ComputeAdditionalInformation(in item, itemDocument); _secondarySort = new Lazy(ComputeSecondarySort); - Language = itemDocument.Project.Language; } - public string Language { get; } - private static string ComputeAdditionalInformation(in RoslynNavigateToItem item, Document itemDocument) { // For partial types, state what file they're in so the user can disambiguate the results. diff --git a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs index 42525a23e8f98..4b3522f067936 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; @@ -52,7 +51,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -63,7 +62,7 @@ public async Task SearchProjectsAsync( { 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); + await onResultFound(new InternalFSharpNavigateToSearchResult(result)).ConfigureAwait(false); await onProjectCompleted().ConfigureAwait(false); } diff --git a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs index 0f5bcdcd96a74..2aa0ccb9a2203 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,7 +26,7 @@ public static Task SearchAsync( var searcher = NavigateToSearcher.Create( solution, AsynchronousOperationListenerProvider.NullListener, - new OmniSharpNavigateToCallbackImpl(callback), + new OmniSharpNavigateToCallbackImpl(solution, callback), searchPattern, kinds, disposalToken: CancellationToken.None); @@ -33,10 +34,11 @@ public static Task SearchAsync( return searcher.SearchAsync(NavigateToSearchScope.Solution, cancellationToken); } - private sealed class OmniSharpNavigateToCallbackImpl(OmniSharpNavigateToCallback callback) : INavigateToSearchCallback + private sealed class OmniSharpNavigateToCallbackImpl(Solution solution, OmniSharpNavigateToCallback callback) : INavigateToSearchCallback { - public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) { + 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, diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index be535cf145ab5..06550cec51760 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -229,7 +229,7 @@ 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); diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs index b1458f7836aa3..f028cab986920 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs @@ -6,6 +6,7 @@ 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 +22,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; } @@ -65,6 +69,7 @@ public Task AddItemAsync(INavigateToSearchResult result, CancellationToken cance // api). var perProviderItemPriority = float.MaxValue - Enumerable.Sum(result.Matches.Select(m => (int)m.Kind)); + var project = _solution.GetRequiredProject(result.NavigableItem.Document.Project.Id); _searchCallback.AddItem(new RoslynCodeSearchResult( _provider, result, @@ -74,7 +79,7 @@ public Task AddItemAsync(INavigateToSearchResult result, CancellationToken cance matches, result.NavigableItem.Document.FilePath, perProviderItemPriority, - result.Language)); + project.Language)); return Task.CompletedTask; } diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs index d535811c7cea4..c0d8b86af61c9 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynSearchItemsSource.cs @@ -89,10 +89,11 @@ private async Task PerformSearchWorkerAsync( // 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); diff --git a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs index 0d60c8feb2a90..2c95f2f1c2315 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphQueries/SearchGraphQuery.cs @@ -19,7 +19,7 @@ internal sealed partial class SearchGraphQuery( 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 From bbc2d2e04fd6bda42cb0a5f3aa772cff0b2bf9b1 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 26 Apr 2024 13:17:25 -0700 Subject: [PATCH 0875/1047] Update tests to better represent current behavior --- .../AbstractPullDiagnosticTestsBase.cs | 41 ++--- .../Diagnostics/NonLocalDiagnosticTests.cs | 7 +- .../Diagnostics/PullDiagnosticTests.cs | 164 +++++------------- 3 files changed, 73 insertions(+), 139 deletions(-) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 73231c4dc1bf1..556c9a4cc1282 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -72,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); } } @@ -114,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(); @@ -121,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) @@ -147,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 { @@ -158,7 +160,8 @@ private static WorkspaceDiagnosticParams CreateProposedWorkspaceDiagnosticParams return new WorkspaceDiagnosticParams { PreviousResultId = previousResultsLsp, - PartialResultToken = progress + PartialResultToken = progress, + Identifier = category }; } @@ -166,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( @@ -231,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( @@ -243,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, @@ -271,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) { @@ -298,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 ? PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal : null, + Identifier = category, PreviousResultId = previousResultId, PartialResultToken = progress, TextDocument = vsTextDocumentIdentifier, @@ -361,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/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 6f32ed5d1bb8a..1141ce6e1a12d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -73,14 +73,14 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/fsharp/issues/15972")] - public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool mutatingLspWorkspace) + 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); + markup, mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, additionalAnalyzers: additionalAnalyzers); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -90,35 +90,35 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff_Categories(bool await OpenDocumentAsync(testLspServer, document); var syntaxResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentCompilerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSyntax); var semanticResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentCompilerSemantic); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentCompilerSemantic); 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); + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSyntax); var semanticResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: semanticResults.Single().ResultId, category: PullDiagnosticCategories.DocumentCompilerSemantic); + 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); var syntaxAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); var semanticAnalyzerResults = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); + 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); var syntaxAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); + testLspServer, document.GetURI(), useVSDiagnostics, previousResultId: syntaxAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSyntax); var semanticAnalyzerResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics: true, previousResultId: semanticAnalyzerResults.Single().ResultId, category: PullDiagnosticCategories.DocumentAnalyzerSemantic); + 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); @@ -181,37 +181,14 @@ static void Main(string[] args) } [Theory, CombinatorialData] - public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_NoCategory(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(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); - } - - [Theory, CombinatorialData] - public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile_Category(bool useVSDiagnostics, 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(); @@ -221,17 +198,10 @@ class A { await OpenDocumentAsync(testLspServer, document); var results = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, document.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.Task); + 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] @@ -1035,7 +1005,7 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithWithRunCodeAnalysisF } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool mutatingLspWorkspace) { var markup1 = @" @@ -1043,15 +1013,15 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool useVS class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + [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); } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool mutatingLspWorkspace) { var markup1 = @" @@ -1059,21 +1029,14 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool useVSD class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + [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] @@ -1108,7 +1071,7 @@ class A { } [Theory, CombinatorialData] - public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool useVSDiagnostics, bool mutatingLspWorkspace) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool mutatingLspWorkspace) { var markup1 = @" @@ -1116,24 +1079,15 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool useVSD class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + [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) + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) { var markup1 = @" @@ -1141,28 +1095,18 @@ public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool useVSDi class A { }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - Assert.Equal("TODO", results[0].Diagnostics.Single().Code); - Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); - } - else - { - Assert.Equal(2, results.Length); + Assert.Equal(1, 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) + public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool mutatingLspWorkspace) { var markup1 = @" @@ -1170,24 +1114,12 @@ public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn class A { "; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( - [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); - - var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - - if (useVSDiagnostics) - { - Assert.Equal(1, results.Length); - - Assert.Equal("TODO", results[0].Diagnostics![0].Code); - } - else - { - Assert.Equal(2, results.Length); + [markup1], mutatingLspWorkspace, BackgroundAnalysisScope.FullSolution, useVSDiagnostics: true); - Assert.Equal("CS1513", results[0].Diagnostics![0].Code); + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: true, category: PullDiagnosticCategories.Task); - Assert.Empty(results[1].Diagnostics); - } + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics![0].Code); } [Theory, CombinatorialData] @@ -1206,9 +1138,9 @@ public async Task EditAndContinue_NoActiveSession(bool mutatingLspWorkspace) } [Theory, CombinatorialData] - public async Task EditAndContinue(bool mutatingLspWorkspace) + public async Task EditAndContinue(bool useVSDiagnostics, bool mutatingLspWorkspace) { - var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics: true); + var options = GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, compilerDiagnosticsScope: null, useVSDiagnostics); var composition = Composition .AddExcludedPartTypes(typeof(EditAndContinueService)) .AddParts(typeof(MockEditAndContinueService)); @@ -1234,7 +1166,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) encSessionState.ApplyChangesDiagnostics = [projectDiagnostic, openDocumentDiagnostic1, closedDocumentDiagnostic]; encService.GetDocumentDiagnosticsImpl = (_, _) => [openDocumentDiagnostic2]; - var documentResults1 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, openDocument.GetURI(), useVSDiagnostics: true, category: PullDiagnosticCategories.EditAndContinue); + var documentResults1 = await RunGetDocumentPullDiagnosticsAsync(testLspServer, openDocument.GetURI(), useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); // both diagnostics located in the open document are reported: AssertEx.Equal( @@ -1242,7 +1174,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) "file:///C:/test1.cs -> [ENC_OPEN_DOC1,ENC_OPEN_DOC2]", ], documentResults1.Select(Inspect)); - var workspaceResults1 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics: true, includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + var workspaceResults1 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ @@ -1256,7 +1188,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) diagnosticsRefresher.RequestWorkspaceRefresh(); var documentResults2 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, openDocument.GetURI(), previousResultId: documentResults1.Single().ResultId, useVSDiagnostics: true, category: PullDiagnosticCategories.EditAndContinue); + testLspServer, openDocument.GetURI(), previousResultId: documentResults1.Single().ResultId, useVSDiagnostics: useVSDiagnostics, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ @@ -1264,7 +1196,7 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) ], documentResults2.Select(Inspect)); var workspaceResults2 = await RunGetWorkspacePullDiagnosticsAsync( - testLspServer, useVSDiagnostics: true, previousResults: workspaceResults1.SelectAsArray(r => (r.ResultId, r.TextDocument)), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(workspaceResults1), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); AssertEx.Equal( [ "file:///C:/test2.cs -> []", @@ -1277,14 +1209,14 @@ public async Task EditAndContinue(bool mutatingLspWorkspace) diagnosticsRefresher.RequestWorkspaceRefresh(); var documentResults3 = await RunGetDocumentPullDiagnosticsAsync( - testLspServer, openDocument.GetURI(), previousResultId: documentResults2.Single().ResultId, useVSDiagnostics: true, category: PullDiagnosticCategories.EditAndContinue); + 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: true, previousResults: workspaceResults2.SelectAsArray(r => (r.ResultId, r.TextDocument)), includeTaskListItems: false, category: PullDiagnosticCategories.EditAndContinue); + 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) From 3eefb58ea69229a9374d14a51704c011877ead52 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 26 Apr 2024 14:06:16 -0700 Subject: [PATCH 0876/1047] Fix LSP File watching so it correctly reports the baseUri if there's a trailing slash (#73203) Fix LSP File watching so it correctly reports the baseUri if there's a trailing slash --- .../LspFileChangeWatcherTests.cs | 2 +- .../FileWatching/LspFileChangeWatcher.cs | 11 ++--- .../Extensions/ProtocolConversions.cs | 16 ++++++++ .../ProtocolConversionsTests.cs | 41 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) 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/HostWorkspace/FileWatching/LspFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs index b2da22f5e8157..254707da542a7 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs @@ -65,8 +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); - private static readonly StringComparer _stringComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + 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) { @@ -80,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(); @@ -99,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); } @@ -128,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/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index a06cee02e928e..b46fbf4e76b96 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -199,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) { 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")] From bd61fd553ab91b63e36dd62aa91f20050bed50c4 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 26 Apr 2024 14:35:59 -0700 Subject: [PATCH 0877/1047] Fix dynamic registration to only register once per source and add test --- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 27 +++- ...cePullDiagnosticsHandler_IOnInitialized.cs | 35 ------ .../DiagnosticRegistrationTests.cs | 116 ++++++++++++++++++ 3 files changed, 137 insertions(+), 41 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs create mode 100644 src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs 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 9720adda62bfd..539100d7a23b2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed @@ -17,15 +18,29 @@ internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitiali { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - // Dynamically register for all of our document diagnostic sources. + // 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. - // Task diagnostics shouldn't be reported through VSCode (it has its own task stuff). Additional cleanup needed. - var sources = DiagnosticSourceManager.GetDocumentSourceProviderNames(clientCapabilities); - var registrations = sources.Select(FromSourceName).ToArray(); + 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() @@ -35,13 +50,13 @@ await _clientLanguageServerManager.SendRequestAsync( cancellationToken).ConfigureAwait(false); } - Registration FromSourceName(string sourceName) + static Registration FromSourceName((string Name, bool IsWorkspaceSource) source) { return new() { Id = Guid.NewGuid().ToString(), Method = Methods.TextDocumentDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkspaceDiagnostics = false } + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = source.Name, InterFileDependencies = true, WorkspaceDiagnostics = source.IsWorkspaceSource, WorkDoneProgress = source.IsWorkspaceSource } }; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs deleted file mode 100644 index 44c880b7680e2..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitialized -{ - public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) - { - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) - { - var providerNames = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(clientCapabilities); - await _clientLanguageServerManager.SendRequestAsync( - methodName: Methods.ClientRegisterCapabilityName, - @params: new RegistrationParams - { - Registrations = providerNames.Select(name => new Registration - { - // Due to https://github.com/microsoft/language-server-protocol/issues/1723 - // we need to use textDocument/diagnostic instead of workspace/diagnostic - Method = Methods.TextDocumentDiagnosticName, - Id = name, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = name, InterFileDependencies = true, WorkDoneProgress = true, WorkspaceDiagnostics = true } - }).ToArray() - }, - cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs new file mode 100644 index 0000000000000..7cfc71557b925 --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .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(); + } + } +} From b14801bf5d01a1ae4f8e08e43472226502ea8ae7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 14:42:18 -0700 Subject: [PATCH 0878/1047] Renames and arrays --- .../NavigateTo/NavigateToSearcherTests.cs | 66 +++++++++---------- .../NavigateToItemProvider.Callback.cs | 57 ++++++++-------- .../VSTypeScriptNavigateToSearchService.cs | 13 ++-- ...ateToSearchService.CachedDocumentSearch.cs | 18 ++--- ...ToSearchService.GeneratedDocumentSearch.cs | 12 ++-- ...stractNavigateToSearchService.InProcess.cs | 22 +++---- ...actNavigateToSearchService.NormalSearch.cs | 24 +++---- .../AbstractNavigateToSearchService.cs | 21 ++++-- .../NavigateTo/INavigateToSearchCallback.cs | 3 +- .../NavigateTo/INavigateToSearchService.cs | 8 +-- .../IRemoteNavigateToSearchService.cs | 12 ++-- .../Portable/NavigateTo/NavigateToSearcher.cs | 32 +++++---- .../Symbols/WorkspaceSymbolsHandler.cs | 25 +++---- .../FSharpNavigateToSearchService.cs | 12 ++-- .../OmniSharpNavigateToSearchService.cs | 31 +++++---- .../IdeCoreBenchmarks/NavigateToBenchmarks.cs | 2 +- .../RoslynNavigateToSearchCallback.cs | 38 ++++++----- .../ProgressionNavigateToSearchCallback.cs | 16 +++-- .../RemoteNavigateToSearchService.cs | 24 +++---- 19 files changed, 236 insertions(+), 200 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index eedd1595855db..cf59f9601eb94 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -30,7 +30,7 @@ private static void SetupSearchProject( Mock searchService, string pattern, bool isFullyLoaded, - INavigateToSearchResult? result) + ImmutableArray results) { if (isFullyLoaded) { @@ -42,7 +42,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -51,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( @@ -65,7 +65,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -73,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. @@ -92,7 +92,7 @@ private static void SetupSearchProject( pattern, ImmutableHashSet.Empty, It.IsAny(), - It.IsAny>(), + It.IsAny, Task>>(), It.IsAny>(), It.IsAny())).Callback( (Solution solution, @@ -101,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); } } @@ -121,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); @@ -134,7 +134,7 @@ 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(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)); @@ -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(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. @@ -201,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); @@ -233,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); @@ -247,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(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. @@ -269,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)); @@ -280,7 +280,7 @@ 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(result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); @@ -317,7 +317,7 @@ public class D """, composition: FirstActiveAndVisibleComposition); 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)); @@ -338,7 +338,7 @@ public class D 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(result, It.IsAny())).Returns(Task.CompletedTask); + callbackMock.Setup(c => c.AddResultsAsync(results, It.IsAny())).Returns(Task.CompletedTask); callbackMock.Setup(c => c.Done(true)); @@ -365,25 +365,25 @@ private sealed class MockAdvancedNavigateToSearchService : IAdvancedNavigateToSe 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + 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 onResultFound, CancellationToken cancellationToken) + 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + 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 onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) + 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; diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index 388a1b1f4da63..cb42cc3076b4f 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.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; @@ -42,37 +43,41 @@ public void Done(bool isFullyLoaded) } } - public Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) + public Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var matchedSpans = result.NameMatchSpans.SelectAsArray(t => t.ToSpan()); + 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 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); + 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. + 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; } diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptNavigateToSearchService.cs index 849b5248a1656..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(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/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 63a41e9cbbab8..a44c47389b276 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -63,7 +63,7 @@ public async Task SearchCachedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -73,7 +73,7 @@ public async Task SearchCachedDocumentsAsync( 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,7 +81,7 @@ 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], callbackId, cancellationToken), @@ -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,7 +101,7 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( ImmutableArray priorityDocumentKeys, string searchPattern, IImmutableSet kinds, - Func onItemFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -153,11 +153,11 @@ async Task ProcessProjectGroupAsync(IGrouping group) await SearchCachedDocumentsInCurrentProcessAsync( storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); + onItemsFound, highPriDocs, cancellationToken).ConfigureAwait(false); await SearchCachedDocumentsInCurrentProcessAsync( storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + onItemsFound, lowPriDocs, cancellationToken).ConfigureAwait(false); // done with project. Let the host know. await onProjectCompleted().ConfigureAwait(false); @@ -169,7 +169,7 @@ private static async Task SearchCachedDocumentsInCurrentProcessAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, + Func, Task> onItemsFound, HashSet documentKeys, CancellationToken cancellationToken) { @@ -185,7 +185,7 @@ private static async Task SearchCachedDocumentsInCurrentProcessAsync( return; await ProcessIndexAsync( - documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken).ConfigureAwait(false); + documentKey, document: null, patternName, patternContainer, kinds, onItemsFound, index, cancellationToken).ConfigureAwait(false); }, cancellationToken)); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 9e0d9c9304724..8fac90140cf5f 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -21,7 +21,7 @@ public async Task SearchGeneratedDocumentsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -29,12 +29,12 @@ public async Task SearchGeneratedDocumentsAsync( 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, @@ -50,14 +50,14 @@ await client.TryInvokeAsync( } 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) { @@ -76,7 +76,7 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( using var _ = GetPooledHashSet(sourceGeneratedDocs, out var documents); await ProcessDocumentsAsync( - searchDocument: null, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, documents, cancellationToken).ConfigureAwait(false); + searchDocument: null, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemsFound, documents, 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 5224895958359..da3b453bcf6de 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -40,7 +40,7 @@ internal abstract partial class AbstractNavigateToSearchService private static async Task SearchProjectInCurrentProcessAsync( Project project, ImmutableArray priorityDocuments, Document? searchDocument, string pattern, IImmutableSet kinds, - Func onResultFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -62,10 +62,10 @@ private static async Task SearchProjectInCurrentProcessAsync( 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); + await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemsFound, highPriDocs, cancellationToken).ConfigureAwait(false); // Then process non-priority documents. - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onResultFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemsFound, lowPriDocs, cancellationToken).ConfigureAwait(false); } finally { @@ -78,7 +78,7 @@ private static async Task ProcessDocumentsAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, + Func, Task> onItemsFound, HashSet documents, CancellationToken cancellationToken) { @@ -91,7 +91,7 @@ private static async Task ProcessDocumentsAsync( continue; cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemFound, cancellationToken)); + tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemsFound, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -102,7 +102,7 @@ private static async Task ProcessDocumentAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onResultFound, + Func, Task> onItemsFound, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -110,7 +110,7 @@ private static async Task ProcessDocumentAsync( var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); await ProcessIndexAsync( - DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onResultFound, index, cancellationToken).ConfigureAwait(false); + DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onItemsFound, index, cancellationToken).ConfigureAwait(false); } private static async Task ProcessIndexAsync( @@ -119,7 +119,7 @@ private static async Task ProcessIndexAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onResultFound, + Func, Task> onItemsFound, TopLevelSyntaxTreeIndex index, CancellationToken cancellationToken) { @@ -141,7 +141,7 @@ await AddResultIfMatchAsync( documentKey, document, declaredSymbolInfo, nameMatcher, containerMatcher, - kinds, onResultFound, cancellationToken).ConfigureAwait(false); + kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } } @@ -152,7 +152,7 @@ private static async Task AddResultIfMatchAsync( PatternMatcher nameMatcher, PatternMatcher? containerMatcher, DeclaredSymbolInfoKindSet kinds, - Func onResultFound, + Func, Task> onItemsFound, CancellationToken cancellationToken) { using var nameMatches = TemporaryArray.Empty; @@ -176,7 +176,7 @@ private static async Task AddResultIfMatchAsync( var result = ConvertResult( documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); - await onResultFound(result).ConfigureAwait(false); + await onItemsFound([result]).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index a954cc94f2435..05fcd08315314 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -21,16 +21,16 @@ 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, onResultFound, 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, @@ -41,14 +41,14 @@ await client.TryInvokeAsync( 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 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); + onItemsFound, () => Task.CompletedTask, cancellationToken); } public async Task SearchProjectsAsync( @@ -58,7 +58,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -67,13 +67,13 @@ public async Task SearchProjectsAsync( 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 @@ -88,7 +88,7 @@ await client.TryInvokeAsync( } 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,7 +96,7 @@ public static async Task SearchProjectsInCurrentProcessAsync( ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, - Func onItemFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -120,7 +120,7 @@ async Task ProcessProjectsAsync(HashSet projects) cancellationToken.ThrowIfCancellationRequested(); tasks.Add(SearchProjectInCurrentProcessAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); + searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 2feb9aadf401d..d1458527fa2f5 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -33,14 +32,22 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig 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 => { - var result = await item.TryCreateSearchResultAsync(solution, activeDocument, cancellationToken).ConfigureAwait(false); - if (result != null) - await onResultFound(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); }; } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchCallback.cs index 1a953b5fc6dfb..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(INavigateToSearchResult result, CancellationToken cancellationToken); + Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken); void ReportProgress(int current, int maximum); } diff --git a/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/INavigateToSearchService.cs index f798c1b136bb5..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 c8c64c67a639a..2d50b244face1 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -191,7 +191,7 @@ private async Task SearchCurrentDocumentAsync(CancellationToken cancellationToke await AddProgressItemsAsync(1, cancellationToken).ConfigureAwait(false); await service.SearchDocumentAsync( _activeDocument, _searchPattern, _kinds, - r => _callback.AddItemAsync(r, cancellationToken), + r => _callback.AddResultsAsync(r, cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -345,8 +345,8 @@ private ImmutableArray GetPriorityDocuments(ImmutableArray pr 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 @@ -382,18 +382,26 @@ async Task SearchCoreAsync(IGrouping grouping await processProjectAsync( searchService, [.. grouping], - result => + 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(result, cancellationToken); + if (nonDuplicates.Count > 0) + _callback.AddResultsAsync(nonDuplicates.ToImmutableAndClear(), cancellationToken); + + return Task.CompletedTask; }, () => this.ProgressItemsCompletedAsync(count: 1, cancellationToken)).ConfigureAwait(false); } @@ -430,7 +438,7 @@ private Task SearchCachedDocumentsAsync( parallel: true, orderedProjects, seenItems, - async (service, projects, onItemFound, onProjectCompleted) => + async (service, projects, onItemsFound, onProjectCompleted) => { // if the language doesn't support searching cached docs, immediately transition the project to the // completed state. @@ -443,7 +451,7 @@ private Task SearchCachedDocumentsAsync( { await advancedService.SearchCachedDocumentsAsync( _solution, projects, GetPriorityDocuments(projects), _searchPattern, _kinds, _activeDocument, - onItemFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } }, cancellationToken); @@ -519,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> onResultFound, 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> onResultFound, Func onProjectCompleted, CancellationToken cancellationToken) { foreach (var _ in projects) await onProjectCompleted().ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index 3e9b01ebea765..0c05d18f61007 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -70,23 +70,26 @@ private sealed class LSPNavigateToCallback( BufferedProgress progress) : INavigateToSearchCallback { - public async Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); - var solution = context.Solution; - var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); + foreach (var result in results) + { + var solution = context.Solution; + 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 = 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/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs index 4b3522f067936..fe18ef4273008 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs @@ -36,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( @@ -51,7 +51,7 @@ public async Task SearchProjectsAsync( string searchPattern, IImmutableSet kinds, Document? activeDocument, - Func onResultFound, + Func, Task> onResultsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -61,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(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/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs index 2aa0ccb9a2203..49275c191a188 100644 --- a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs @@ -36,22 +36,25 @@ public static Task SearchAsync( private sealed class OmniSharpNavigateToCallbackImpl(Solution solution, OmniSharpNavigateToCallback callback) : INavigateToSearchCallback { - public async Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - 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)); + 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/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index 06550cec51760..f9c73e5a07fde 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -232,7 +232,7 @@ await service.SearchProjectsAsync( r => { lock (results) - results.Add(r); + results.AddRange(r); return Task.CompletedTask; }, diff --git a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs index f028cab986920..cf0b5b0f3cc8b 100644 --- a/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs +++ b/src/VisualStudio/Core/Def/NavigateTo/RoslynNavigateToSearchCallback.cs @@ -3,6 +3,7 @@ // 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; @@ -55,31 +56,34 @@ public void ReportIncomplete() _searchCallback.ReportIncomplete(IncompleteReason.Parsing); } - public Task AddItemAsync(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)); - 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)); + 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/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs b/src/VisualStudio/Core/Def/Progression/GraphQueries/ProgressionNavigateToSearchCallback.cs index 9402ca83293c9..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; @@ -38,14 +39,17 @@ public void ReportIncomplete() { } - public async Task AddItemAsync(INavigateToSearchResult result, CancellationToken cancellationToken) + public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { - var node = await _graphBuilder.CreateNodeAsync(_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/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index e267243f8c32d..e75d2e71600c4 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, _) => + callback.OnItemsFoundAsync(callbackId, i), cancellationToken).ConfigureAwait(false); Func onProjectCompleted = async () => await _callback.InvokeAsync((callback, _) => 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); } } From e9b7afd8e68b25d34f102233782fda1b4c149c07 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 26 Apr 2024 15:00:48 -0700 Subject: [PATCH 0879/1047] Update src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs Co-authored-by: Cyrus Najmabadi --- .../ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs index 7cfc71557b925..cb85dd18a0c98 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/DiagnosticRegistrationTests.cs @@ -17,6 +17,7 @@ using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; + public class DiagnosticRegistrationTests : AbstractLanguageServerProtocolTests { public DiagnosticRegistrationTests(ITestOutputHelper? testOutputHelper) : base(testOutputHelper) From 156dca75c46ea8b1ab0c0a898d77df8512d5eda1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:03:12 -0700 Subject: [PATCH 0880/1047] in progress --- ...ateToSearchService.CachedDocumentSearch.cs | 93 +++++++++++++++---- ...stractNavigateToSearchService.InProcess.cs | 29 +++--- 2 files changed, 92 insertions(+), 30 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index a44c47389b276..ac38d42f01447 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -10,9 +10,11 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; @@ -39,6 +41,8 @@ internal abstract partial class AbstractNavigateToSearchService /// private static StringTable? s_stringTable = new(); + private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; + private static void ClearCachedData() { // Volatiles are technically not necessary due to automatic fencing of reference-type writes. However, @@ -110,10 +114,6 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( 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); - // Process the documents by project group. That way, when each project is done, we can // report that back to the host for progress. var groups = documentKeys.GroupBy(d => d.Project).ToImmutableArray(); @@ -125,25 +125,78 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( 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); + // 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); + + var channel = Channel.CreateUnbounded(s_channelOptions); + + await Task.WhenAll( + FindAllItemsAndWriteToChannelAsync(channel.Writer), + ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader)).ConfigureAwait(false); return; - async Task ProcessProjectGroupsAsync(HashSet> groups) + async Task ReadItemsFromChannelAndReportToCallbackAsync(ChannelReader channelReader) { - cancellationToken.ThrowIfCancellationRequested(); + await Task.Yield().ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var items); + + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a batch. + while (channelReader.TryRead(out var item)) + items.Add(item); + + await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); + } + } + + async Task FindAllItemsAndWriteToChannelAsync(ChannelWriter channelWriter) + { + Exception? exception = null; + try + { + await FindAllItemsAndWriteToChannelWorkerAsync(item => channel.Writer.TryWrite(item)).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. + channelWriter.TryComplete(exception); + } + } + + async Task FindAllItemsAndWriteToChannelWorkerAsync(Action onItemFound) + { + await Task.Yield().ConfigureAwait(false); + + await ProcessProjectGroupsAsync(highPriorityGroups, onItemFound).ConfigureAwait(false); + await ProcessProjectGroupsAsync(lowPriorityGroups, onItemFound).ConfigureAwait(false); + } + + async Task ProcessProjectGroupsAsync(HashSet> groups, Action onItemFound) + { + if (cancellationToken.IsCancellationRequested) + return; + using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var group in groups) - tasks.Add(ProcessProjectGroupAsync(group)); + tasks.Add(ProcessProjectGroupAsync(group, onItemFound)); await Task.WhenAll(tasks).ConfigureAwait(false); } - async Task ProcessProjectGroupAsync(IGrouping group) + async Task ProcessProjectGroupAsync(IGrouping group, Action onItemFound) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + await Task.Yield().ConfigureAwait(false); var project = group.Key; @@ -153,11 +206,11 @@ async Task ProcessProjectGroupAsync(IGrouping group) await SearchCachedDocumentsInCurrentProcessAsync( storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemsFound, highPriDocs, cancellationToken).ConfigureAwait(false); + onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); await SearchCachedDocumentsInCurrentProcessAsync( storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemsFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); // done with project. Let the host know. await onProjectCompleted().ConfigureAwait(false); @@ -169,11 +222,13 @@ private static async Task SearchCachedDocumentsInCurrentProcessAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func, Task> onItemsFound, + Func onItemFound, HashSet documentKeys, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var documentKey in documentKeys) @@ -184,8 +239,8 @@ private static async Task SearchCachedDocumentsInCurrentProcessAsync( if (index == null) return; - await ProcessIndexAsync( - documentKey, document: null, patternName, patternContainer, kinds, onItemsFound, index, cancellationToken).ConfigureAwait(false); + ProcessIndex( + documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken).ConfigureAwait(false); }, cancellationToken)); } @@ -197,7 +252,9 @@ await ProcessIndexAsync( 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.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index da3b453bcf6de..cde62fef12413 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -113,17 +113,19 @@ await ProcessIndexAsync( DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onItemsFound, index, cancellationToken).ConfigureAwait(false); } - private static async Task ProcessIndexAsync( + private static void ProcessIndex( DocumentKey documentKey, Document? document, string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func, Task> onItemsFound, + Func onItemFound, TopLevelSyntaxTreeIndex index, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + var containerMatcher = patternContainer != null ? PatternMatcher.CreateDotSeparatedContainerMatcher(patternContainer, includeMatchedSpans: true) : null; @@ -137,22 +139,22 @@ private static async Task ProcessIndexAsync( if (declaredSymbolInfo.Kind == DeclaredSymbolInfoKind.Namespace) continue; - await AddResultIfMatchAsync( + AddResultIfMatch( documentKey, document, declaredSymbolInfo, nameMatcher, containerMatcher, - kinds, onItemsFound, cancellationToken).ConfigureAwait(false); + kinds, onItemFound, cancellationToken); } } - private static async Task AddResultIfMatchAsync( + private static void AddResultIfMatch( DocumentKey documentKey, Document? document, DeclaredSymbolInfo declaredSymbolInfo, PatternMatcher nameMatcher, PatternMatcher? containerMatcher, DeclaredSymbolInfoKindSet kinds, - Func, Task> onItemsFound, + Func onItemFound, CancellationToken cancellationToken) { using var nameMatches = TemporaryArray.Empty; @@ -176,7 +178,7 @@ private static async Task AddResultIfMatchAsync( var result = ConvertResult( documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); - await onItemsFound([result]).ConfigureAwait(false); + onItemFound(result); } } @@ -217,20 +219,23 @@ private static RoslynNavigateToItem ConvertResult( allPatternMatches.ToImmutableAndClear()); } - private static async ValueTask> GetAdditionalProjectsWithMatchAsync( + private static ImmutableArray GetAdditionalProjectsWithMatchAsync( Document? document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) { if (document == null) return []; - using var _ = ArrayBuilder.GetInstance(out var result); - var solution = document.Project.Solution; var linkedDocumentIds = document.GetLinkedDocumentIds(); + if (linkedDocumentIds.Length == 0) + return []; + + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var linkedDocumentId in linkedDocumentIds) { var linkedDocument = solution.GetRequiredDocument(linkedDocumentId); - var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).ConfigureAwait(false); + var index = TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); // 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. From 949dc9379a3abddc438e2efd26aaec6d35b3aa87 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:05:29 -0700 Subject: [PATCH 0881/1047] in progress --- ...stractNavigateToSearchService.InProcess.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index cde62fef12413..6ecde8175ef74 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -82,7 +82,9 @@ private static async Task ProcessDocumentsAsync( HashSet documents, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var document in documents) @@ -90,7 +92,9 @@ private static async Task ProcessDocumentsAsync( if (searchDocument != null && searchDocument != document) continue; - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemsFound, cancellationToken)); } @@ -105,12 +109,15 @@ private static async Task ProcessDocumentAsync( Func, Task> onItemsFound, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + await Task.Yield().ConfigureAwait(false); var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); - await ProcessIndexAsync( - DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onItemsFound, index, cancellationToken).ConfigureAwait(false); + ProcessIndex( + DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, + item => onItemsFound([item]), index, cancellationToken); } private static void ProcessIndex( @@ -135,6 +142,9 @@ private static void ProcessIndex( 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; @@ -157,6 +167,9 @@ private static void AddResultIfMatch( Func onItemFound, CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + return; + using var nameMatches = TemporaryArray.Empty; using var containerMatches = TemporaryArray.Empty; @@ -173,8 +186,7 @@ private static void AddResultIfMatch( // 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 additionalMatchingProjects = GetAdditionalProjectsWithMatch(document, declaredSymbolInfo, cancellationToken); var result = ConvertResult( documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); @@ -219,7 +231,7 @@ private static RoslynNavigateToItem ConvertResult( allPatternMatches.ToImmutableAndClear()); } - private static ImmutableArray GetAdditionalProjectsWithMatchAsync( + private static ImmutableArray GetAdditionalProjectsWithMatch( Document? document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) { if (document == null) From 6bb84e261da3ceebc5ea125d0a23bb49df8c854a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:06:51 -0700 Subject: [PATCH 0882/1047] in progress --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 4 ++-- .../NavigateTo/AbstractNavigateToSearchService.InProcess.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index ac38d42f01447..b17db518220d4 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -222,7 +222,7 @@ private static async Task SearchCachedDocumentsInCurrentProcessAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, + Action onItemFound, HashSet documentKeys, CancellationToken cancellationToken) { @@ -240,7 +240,7 @@ private static async Task SearchCachedDocumentsInCurrentProcessAsync( return; ProcessIndex( - documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken).ConfigureAwait(false); + documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken); }, cancellationToken)); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 6ecde8175ef74..33d9dc3448151 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -126,7 +126,7 @@ private static void ProcessIndex( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, + Action onItemFound, TopLevelSyntaxTreeIndex index, CancellationToken cancellationToken) { @@ -164,7 +164,7 @@ private static void AddResultIfMatch( PatternMatcher nameMatcher, PatternMatcher? containerMatcher, DeclaredSymbolInfoKindSet kinds, - Func onItemFound, + Action onItemFound, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) From 10a3642ef734a75cd666ee0f9fc9be8729338473 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:08:36 -0700 Subject: [PATCH 0883/1047] in progress --- ...bstractNavigateToSearchService.InProcess.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 33d9dc3448151..29ec1dd75b680 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -40,14 +40,15 @@ internal abstract partial class AbstractNavigateToSearchService private static async Task SearchProjectInCurrentProcessAsync( Project project, ImmutableArray priorityDocuments, Document? searchDocument, string pattern, IImmutableSet kinds, - Func, Task> onItemsFound, + Action onItemFound, Func onProjectCompleted, CancellationToken cancellationToken) { try { await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; // We're doing a real search over the fully loaded solution now. No need to hold onto the cached map // of potentially stale indices. @@ -62,10 +63,10 @@ private static async Task SearchProjectInCurrentProcessAsync( 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, onItemsFound, highPriDocs, cancellationToken).ConfigureAwait(false); + await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); // Then process non-priority documents. - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemsFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); } finally { @@ -78,7 +79,7 @@ private static async Task ProcessDocumentsAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func, Task> onItemsFound, + Action onItemFound, HashSet documents, CancellationToken cancellationToken) { @@ -95,7 +96,7 @@ private static async Task ProcessDocumentsAsync( if (cancellationToken.IsCancellationRequested) return; - tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemsFound, cancellationToken)); + tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemFound, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -106,7 +107,7 @@ private static async Task ProcessDocumentAsync( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Func, Task> onItemsFound, + Action onItemFound, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -116,8 +117,7 @@ private static async Task ProcessDocumentAsync( var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); ProcessIndex( - DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, - item => onItemsFound([item]), index, cancellationToken); + DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onItemFound, index, cancellationToken); } private static void ProcessIndex( From 6d5be9e012613cc486176d7ce16c52440c67dbaa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:13:16 -0700 Subject: [PATCH 0884/1047] in progress --- ...ateToSearchService.CachedDocumentSearch.cs | 48 ++++--------------- ...ToSearchService.GeneratedDocumentSearch.cs | 9 ++++ .../AbstractNavigateToSearchService.cs | 40 ++++++++++++++++ 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index b17db518220d4..3a9fdcf64ae2e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -71,7 +71,9 @@ public async Task SearchCachedDocumentsAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); @@ -109,7 +111,9 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + // Quick abort if OOP is now fully loaded. if (!ShouldSearchCachedDocuments(out _, out _)) return; @@ -132,46 +136,12 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer), - ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader)).ConfigureAwait(false); + FindAllItemsAndWriteToChannelAsync(channel.Writer, ProcessBothProjectGroupsAsync), + ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - async Task ReadItemsFromChannelAndReportToCallbackAsync(ChannelReader channelReader) - { - await Task.Yield().ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var items); - - while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - // Grab as many items as we can from the channel at once and report in a batch. - while (channelReader.TryRead(out var item)) - items.Add(item); - - await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); - } - } - - async Task FindAllItemsAndWriteToChannelAsync(ChannelWriter channelWriter) - { - Exception? exception = null; - try - { - await FindAllItemsAndWriteToChannelWorkerAsync(item => channel.Writer.TryWrite(item)).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. - channelWriter.TryComplete(exception); - } - } - - async Task FindAllItemsAndWriteToChannelWorkerAsync(Action onItemFound) + async Task ProcessBothProjectGroupsAsync(Action onItemFound) { await Task.Yield().ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 8fac90140cf5f..26a9db6765d53 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; @@ -62,6 +63,14 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + + var channel = Channel.CreateUnbounded(s_channelOptions); + + await Task.WhenAll( + FindAllItemsAndWriteToChannelAsync(channel.Writer), + ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader)).ConfigureAwait(false); + + // 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); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index d1458527fa2f5..b4858113e1ea7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -64,4 +65,43 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr instance.AddRange(items); return disposer; } + + private static async Task ReadItemsFromChannelAndReportToCallbackAsync( + ChannelReader channelReader, + Func, Task> onItemsFound, + CancellationToken cancellationToken) + { + await Task.Yield().ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var items); + + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a batch. + while (channelReader.TryRead(out var item)) + items.Add(item); + + await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); + } + } + + private static async Task FindAllItemsAndWriteToChannelAsync( + ChannelWriter channelWriter, + Func, Task> findWorker) + { + Exception? exception = null; + try + { + await findWorker(item => channelWriter.TryWrite(item)).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. + channelWriter.TryComplete(exception); + } + } } From b1ea829378ffd1721119b98d43bf9668087e1057 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:14:21 -0700 Subject: [PATCH 0885/1047] COntinue --- ...ToSearchService.GeneratedDocumentSearch.cs | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 26a9db6765d53..88e66a79adb54 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -67,27 +67,31 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer), - ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader)).ConfigureAwait(false); + FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchProjectsAsync), + ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); + return; - // 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) + async Task SearchProjectsAsync(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, onItemsFound, documents, cancellationToken).ConfigureAwait(false); - - await onProjectCompleted().ConfigureAwait(false); + // 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) + { + 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 onProjectCompleted().ConfigureAwait(false); + } } } } From 6ba9cfce03df092ee6d3b3529a015e3e2ea635d6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 15:19:16 -0700 Subject: [PATCH 0886/1047] Move to channels --- ...ToSearchService.GeneratedDocumentSearch.cs | 11 +++-- ...stractNavigateToSearchService.InProcess.cs | 4 +- ...actNavigateToSearchService.NormalSearch.cs | 45 +++++++++++++------ 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 88e66a79adb54..406f2bb4ba978 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -26,7 +26,9 @@ public async Task SearchGeneratedDocumentsAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); @@ -62,7 +64,8 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; var channel = Channel.CreateUnbounded(s_channelOptions); @@ -82,7 +85,9 @@ async Task SearchProjectsAsync(Action onItemFound) // compilations are available for later projects when needed. foreach (var project in projects) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + // 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); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 29ec1dd75b680..b7c465c6e288e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -173,11 +173,13 @@ private static void AddResultIfMatch( 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) { + 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. diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 05fcd08315314..3351fef851911 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; @@ -44,11 +45,15 @@ await client.TryInvokeAsync( await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } - public static Task SearchDocumentInCurrentProcessAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) + public static async Task SearchDocumentInCurrentProcessAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) { - return SearchProjectInCurrentProcessAsync( + var results = new ConcurrentSet(); + await SearchProjectInCurrentProcessAsync( document.Project, priorityDocuments: [], document, searchPattern, kinds, - onItemsFound, () => Task.CompletedTask, cancellationToken); + t => results.Add(t), () => Task.CompletedTask, cancellationToken).ConfigureAwait(false); + + if (results.Count > 0) + await onItemsFound(results.ToImmutableArray()).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -62,7 +67,9 @@ public async Task SearchProjectsAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); @@ -100,27 +107,37 @@ public static async Task SearchProjectsInCurrentProcessAsync( 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); + var channel = Channel.CreateUnbounded(s_channelOptions); - Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); - - await ProcessProjectsAsync(highPriProjects).ConfigureAwait(false); - await ProcessProjectsAsync(lowPriProjects).ConfigureAwait(false); + await Task.WhenAll( + FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchProjectsAsync), + ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - async Task ProcessProjectsAsync(HashSet projects) + async Task SearchProjectsAsync(Action onItemFound) + { + 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); + + Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + + await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); + await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); + } + + async Task ProcessProjectsAsync(HashSet projects, Action onItemFound) { using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var project in projects) { - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + return; + tasks.Add(SearchProjectInCurrentProcessAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemsFound, onProjectCompleted, cancellationToken)); + searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); From 125163689342a5dbb046c7d546104433179c74ca Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 26 Apr 2024 15:39:19 -0700 Subject: [PATCH 0887/1047] Fix warning --- .../Public/PublicWorkspacePullDiagnosticHandlerFactory.cs | 3 +-- .../Public/PublicWorkspacePullDiagnosticsHandler.cs | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index 9e11c966f21c4..ea0814316fbfc 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -24,7 +24,6 @@ internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, clientLanguageServerManager, analyzerService, diagnosticSourceManager, 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 15cf5adac2d83..71f78c36902a0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -22,18 +22,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [Method(Methods.WorkspaceDiagnosticName)] internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable { - private readonly IClientLanguageServerManager _clientLanguageServerManager; public PublicWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, - IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) { - _clientLanguageServerManager = clientLanguageServerManager; } protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) From e6ffd9c602dabf97a2c67f32e28fa4fb7ab0566d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 16:23:35 -0700 Subject: [PATCH 0888/1047] Parallel --- .../Portable/Utilities/Parallel.ForEach.cs | 482 ++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs diff --git a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs new file mode 100644 index 0000000000000..f8f59ea0bf858 --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs @@ -0,0 +1,482 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +#pragma warning disable CA1068 // CancellationToken parameters must come last +#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods + +namespace Roslyn.Utilities +{ + internal static partial class Parallel + { + public static Task ForEachAsync(IEnumerable source, Func body) + { + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, default(CancellationToken), body); + } + + public static Task ForEachAsync(IEnumerable source, CancellationToken cancellationToken, Func body) + { + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, cancellationToken, body); + } + + private static Task ForEachAsync(IEnumerable source, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) + { + Debug.Assert(source != null); + Debug.Assert(scheduler != null); + Debug.Assert(body != null); + + // One fast up-front check for cancellation before we start the whole operation. + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // The worker body. Each worker will execute this same body. + Func taskBody = static async o => + { + var state = (SyncForEachAsyncState)o; + var launchedNext = false; + +#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current + try + { + // Continue to loop while there are more elements to be processed. + while (!state.Cancellation.IsCancellationRequested) + { + // Get the next element from the enumerator. This requires asynchronously locking around MoveNext/Current. + TSource element; + await state.AcquireLock(); + try + { + if (state.Cancellation.IsCancellationRequested || // check now that the lock has been acquired + !state.Enumerator.MoveNext()) + { + break; + } + + element = state.Enumerator.Current; + } + finally + { + state.ReleaseLock(); + } + + // If the remaining dop allows it and we've not yet queued the next worker, do so now. We wait + // until after we've grabbed an item from the enumerator to a) avoid unnecessary contention on the + // serialized resource, and b) avoid queueing another work if there aren't any more items. Each worker + // is responsible only for creating the next worker, which in turn means there can't be any contention + // on creating workers (though it's possible one worker could be executing while we're creating the next). + if (!launchedNext) + { + launchedNext = true; + state.QueueWorkerIfDopAvailable(); + } + + // Process the loop body. + await state.LoopBody(element, state.Cancellation.Token); + } + } + catch (Exception e) + { + // Record the failure and then don't let the exception propagate. The last worker to complete + // will propagate exceptions as is appropriate to the top-level task. + state.RecordException(e); + } + finally + { + // If we're the last worker to complete, clean up and complete the operation. + if (state.SignalWorkerCompletedIterating()) + { + try + { + state.Dispose(); + } + catch (Exception e) + { + state.RecordException(e); + } + + // Finally, complete the task returned to the ForEachAsync caller. + // This must be the very last thing done. + state.Complete(); + } + } +#pragma warning restore CA2007 + }; + + try + { + // Construct a state object that encapsulates all state to be passed and shared between + // the workers, and queues the first worker. + var state = new SyncForEachAsyncState(source, taskBody, dop, scheduler, cancellationToken, body); + state.QueueWorkerIfDopAvailable(); + return state.Task; + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + public static Task ForEachAsync(IAsyncEnumerable source, Func body) + { + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, default(CancellationToken), body); + } + + public static Task ForEachAsync(IAsyncEnumerable source, CancellationToken cancellationToken, Func body) + { + return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, cancellationToken, body); + } + + private static Task ForEachAsync(IAsyncEnumerable source, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) + { + Debug.Assert(source != null); + Debug.Assert(scheduler != null); + Debug.Assert(body != null); + + // One fast up-front check for cancellation before we start the whole operation. + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // The worker body. Each worker will execute this same body. + Func taskBody = static async o => + { + var state = (AsyncForEachAsyncState)o; + var launchedNext = false; + +#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current + try + { + // Continue to loop while there are more elements to be processed. + while (!state.Cancellation.IsCancellationRequested) + { + // Get the next element from the enumerator. This requires asynchronously locking around MoveNextAsync/Current. + TSource element; + await state.AcquireLock(); + try + { + if (state.Cancellation.IsCancellationRequested || // check now that the lock has been acquired + !await state.Enumerator.MoveNextAsync()) + { + break; + } + + element = state.Enumerator.Current; + } + finally + { + state.ReleaseLock(); + } + + // If the remaining dop allows it and we've not yet queued the next worker, do so now. We wait + // until after we've grabbed an item from the enumerator to a) avoid unnecessary contention on the + // serialized resource, and b) avoid queueing another work if there aren't any more items. Each worker + // is responsible only for creating the next worker, which in turn means there can't be any contention + // on creating workers (though it's possible one worker could be executing while we're creating the next). + if (!launchedNext) + { + launchedNext = true; + state.QueueWorkerIfDopAvailable(); + } + + // Process the loop body. + await state.LoopBody(element, state.Cancellation.Token); + } + } + catch (Exception e) + { + // Record the failure and then don't let the exception propagate. The last worker to complete + // will propagate exceptions as is appropriate to the top-level task. + state.RecordException(e); + } + finally + { + // If we're the last worker to complete, clean up and complete the operation. + if (state.SignalWorkerCompletedIterating()) + { + try + { + await state.DisposeAsync(); + } + catch (Exception e) + { + state.RecordException(e); + } + + // Finally, complete the task returned to the ForEachAsync caller. + // This must be the very last thing done. + state.Complete(); + } + } +#pragma warning restore CA2007 + }; + + try + { + // Construct a state object that encapsulates all state to be passed and shared between + // the workers, and queues the first worker. + var state = new AsyncForEachAsyncState(source, taskBody, dop, scheduler, cancellationToken, body); + state.QueueWorkerIfDopAvailable(); + return state.Task; + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + /// Gets the default degree of parallelism to use when none is explicitly provided. + private static int DefaultDegreeOfParallelism => Environment.ProcessorCount; + + /// Stores the state associated with a ForEachAsync operation, shared between all its workers. + /// Specifies the type of data being enumerated. + private abstract class ForEachAsyncState : TaskCompletionSource // , IThreadPoolWorkItem + { + /// The caller-provided cancellation token. + private readonly CancellationToken _externalCancellationToken; + /// Registration with caller-provided cancellation token. + protected readonly CancellationTokenRegistration _registration; + /// + /// The delegate to invoke on each worker to run the enumerator processing loop. + /// + /// + /// This could have been an action rather than a func, but it returns a task so that the task body is an async Task + /// method rather than async void, even though the worker body catches all exceptions and the returned Task is ignored. + /// + private readonly Func _taskBody; + /// The on which all work should be performed. + private readonly TaskScheduler _scheduler; + /// The present at the time of the ForEachAsync invocation. This is only used if on the default scheduler. + private readonly ExecutionContext? _executionContext; + /// Semaphore used to provide exclusive access to the enumerator. + private readonly SemaphoreSlim? _lock; + + /// The number of outstanding workers. When this hits 0, the operation has completed. + private int _completionRefCount; + /// Any exceptions incurred during execution. + private List? _exceptions; + /// The number of workers that may still be created. + private int _remainingDop; + + /// The delegate to invoke for each element yielded by the enumerator. + public readonly Func LoopBody; + /// The internal token source used to cancel pending work. + public readonly CancellationTokenSource Cancellation = new CancellationTokenSource(); + + /// Initializes the state object. + protected ForEachAsyncState(Func taskBody, bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) + { + _taskBody = taskBody; + _lock = needsLock ? new SemaphoreSlim(initialCount: 1, maxCount: 1) : null; + _remainingDop = dop < 0 ? DefaultDegreeOfParallelism : dop; + LoopBody = body; + _scheduler = scheduler; + if (scheduler == TaskScheduler.Default) + { + _executionContext = ExecutionContext.Capture(); + } + + _externalCancellationToken = cancellationToken; + _registration = cancellationToken.Register(static o => ((ForEachAsyncState)o!).Cancellation.Cancel(), this); + } + + /// Queues another worker if allowed by the remaining degree of parallelism permitted. + /// This is not thread-safe and must only be invoked by one worker at a time. + public void QueueWorkerIfDopAvailable() + { + if (_remainingDop > 0) + { + _remainingDop--; + + // Queue the invocation of the worker/task body. Note that we explicitly do not pass a cancellation token here, + // as the task body is what's responsible for completing the ForEachAsync task, for decrementing the reference count + // on pending tasks, and for cleaning up state. If a token were passed to StartNew (which simply serves to stop the + // task from starting to execute if it hasn't yet by the time cancellation is requested), all of that logic could be + // skipped, and bad things could ensue, e.g. deadlocks, leaks, etc. Also note that we need to increment the pending + // work item ref count prior to queueing the worker in order to avoid race conditions that could lead to temporarily + // and erroneously bouncing at zero, which would trigger completion too early. + Interlocked.Increment(ref _completionRefCount); + if (_scheduler == TaskScheduler.Default) + { + // If the scheduler is the default, we can avoid the overhead of the StartNew Task by just queueing + // this state object as the work item. + ThreadPool.QueueUserWorkItem(s => ((ForEachAsyncState)s).Execute(), this); + } + else + { + // We're targeting a non-default TaskScheduler, so queue the task body to it. + System.Threading.Tasks.Task.Factory.StartNew(_taskBody!, this, default(CancellationToken), TaskCreationOptions.DenyChildAttach, _scheduler); + } + } + } + + /// Signals that the worker has completed iterating. + /// true if this is the last worker to complete iterating; otherwise, false. + public bool SignalWorkerCompletedIterating() => Interlocked.Decrement(ref _completionRefCount) == 0; + + /// Asynchronously acquires exclusive access to the enumerator. + public Task AcquireLock() + { + // We explicitly don't pass this.Cancellation to WaitAsync. Doing so adds overhead, and it isn't actually + // necessary. All of the operations that monitor the lock are part of the same ForEachAsync operation, and the Task + // returned from ForEachAsync can't complete until all of the constituent operations have completed, including whoever + // holds the lock while this worker is waiting on the lock. Thus, the lock will need to be released for the overall + // operation to complete. Passing the token would allow the overall operation to potentially complete a bit faster in + // the face of cancellation, in exchange for making it a bit slower / more overhead in the common case of cancellation + // not being requested. We want to optimize for the latter. This also then avoids an exception throw / catch when + // cancellation is requested. + Debug.Assert(_lock is not null, "Should only be invoked when _lock is non-null"); + return _lock.WaitAsync(CancellationToken.None); + } + + /// Relinquishes exclusive access to the enumerator. + public void ReleaseLock() + { + Debug.Assert(_lock is not null, "Should only be invoked when _lock is non-null"); + _lock.Release(); + } + + /// Stores an exception and triggers cancellation in order to alert all workers to stop as soon as possible. + /// The exception. + public void RecordException(Exception e) + { + // Store the exception. + lock (this) + { + (_exceptions ??= new List()).Add(e); + } + + // Trigger cancellation of all workers. If cancellation has already been triggered + // due to a previous exception occurring, this is a nop. + try + { + Cancellation.Cancel(); + } + catch (AggregateException ae) + { + // If cancellation callbacks erroneously throw exceptions, include those exceptions in the list. + lock (this) + { + _exceptions.AddRange(ae.InnerExceptions); + } + } + } + + /// Completes the ForEachAsync task based on the status of this state object. + public void Complete() + { + Debug.Assert(_completionRefCount == 0, $"Expected {nameof(_completionRefCount)} == 0, got {_completionRefCount}"); + + bool taskSet; + if (_externalCancellationToken.IsCancellationRequested) + { + // The externally provided token had cancellation requested. Assume that any exceptions + // then are due to that, and just cancel the resulting task. + taskSet = TrySetCanceled(_externalCancellationToken); + } + else if (_exceptions is null) + { + // Everything completed successfully. + Debug.Assert(!Cancellation.IsCancellationRequested); + taskSet = TrySetResult(default(VoidResult)); + } + else + { + // Fail the task with the resulting exceptions. The first should be the initial + // exception that triggered the operation to shut down. The others, if any, may + // include cancellation exceptions from other concurrent operations being canceled + // in response to the primary exception. + taskSet = TrySetException(_exceptions); + } + + Debug.Assert(taskSet, "Complete should only be called once."); + } + + /// Executes the task body using the captured when ForEachAsync was invoked. + public void Execute() + { + Debug.Assert(_scheduler == TaskScheduler.Default, $"Expected {nameof(_scheduler)} == TaskScheduler.Default, got {_scheduler}"); + + if (_executionContext is null) + { + _taskBody(this); + } + else + { + ExecutionContext.Run(_executionContext, static o => ((ForEachAsyncState)o!)._taskBody(o), this); + } + } + } + + private sealed class SyncForEachAsyncState : ForEachAsyncState, IDisposable + { + public readonly IEnumerator Enumerator; + + public SyncForEachAsyncState( + IEnumerable source, Func taskBody, + int dop, TaskScheduler scheduler, CancellationToken cancellationToken, + Func body) + : base(taskBody, needsLock: true, dop, scheduler, cancellationToken, body) + { + Enumerator = source.GetEnumerator(); + } + + public void Dispose() + { + _registration.Dispose(); + Enumerator.Dispose(); + } + } + + private sealed class AsyncForEachAsyncState : ForEachAsyncState, IAsyncDisposable + { + public readonly IAsyncEnumerator Enumerator; + + public AsyncForEachAsyncState( + IAsyncEnumerable source, Func taskBody, + int dop, TaskScheduler scheduler, CancellationToken cancellationToken, + Func body) + : base(taskBody, needsLock: true, dop, scheduler, cancellationToken, body) + { + Enumerator = source.GetAsyncEnumerator(Cancellation.Token); + } + + public ValueTask DisposeAsync() + { + _registration.Dispose(); + return Enumerator.DisposeAsync(); + } + } + + private sealed class ForEachState : ForEachAsyncState, IDisposable + { + public T NextAvailable; + public readonly T ToExclusive; + + public ForEachState( + T fromExclusive, T toExclusive, Func taskBody, + bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, + Func body) + : base(taskBody, needsLock, dop, scheduler, cancellationToken, body) + { + NextAvailable = fromExclusive; + ToExclusive = toExclusive; + } + + public void Dispose() => _registration.Dispose(); + } + } +} + +#endif From e79f9a94a5231912c2eb3c18c0136c7016b58041 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 16:35:36 -0700 Subject: [PATCH 0889/1047] in progrss --- ...stractNavigateToSearchService.InProcess.cs | 3 +- ...actNavigateToSearchService.NormalSearch.cs | 47 +++++++++++++++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index b7c465c6e288e..c5f83e44d88c5 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -102,7 +102,7 @@ private static async Task ProcessDocumentsAsync( await Task.WhenAll(tasks).ConfigureAwait(false); } - private static async Task ProcessDocumentAsync( + private static async ValueTask ProcessDocumentAsync( Document document, string patternName, string? patternContainer, @@ -113,7 +113,6 @@ private static async Task ProcessDocumentAsync( if (cancellationToken.IsCancellationRequested) return; - await Task.Yield().ConfigureAwait(false); var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); ProcessIndex( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 3351fef851911..bb2bcd0a46931 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -10,14 +10,22 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; +#if NET +using Parallel = System.Threading.Tasks.Parallel; +#else +using Parallel = Roslyn.Utilities.Parallel; +#endif + internal abstract partial class AbstractNavigateToSearchService { + public async Task SearchDocumentAsync( Document document, string searchPattern, @@ -107,25 +115,54 @@ public static async Task SearchProjectsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { + ClearCachedData(); + + var orderedDocuments = GetOrderedDocuments(); + var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchProjectsAsync), + FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchDocumentsAsync), ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - async Task SearchProjectsAsync(Action onItemFound) + IEnumerable GetOrderedDocuments() { 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); - Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + using var _3 = PooledHashSet.GetInstance(out var seenDocuments); + - await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); - await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); } + async Task SearchDocumentsAsync( + IEnumerable orderedDocuments, + Action onItemFound) + { + // If the user created a dotted pattern then we'll grab the last part of the name + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + + await Parallel.ForEachAsync( + orderedDocuments, + cancellationToken, + (document, cancellationToken) => + ProcessDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); + } + + //async Task SearchProjectsAsync(Action onItemFound) + //{ + + // Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + + // await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); + // await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); + //} + async Task ProcessProjectsAsync(HashSet projects, Action onItemFound) { using var _ = ArrayBuilder.GetInstance(out var tasks); From 72a217f8b8815803aefbbc1728449a460cdb240f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 16:44:42 -0700 Subject: [PATCH 0890/1047] in progrss --- ...stractNavigateToSearchService.InProcess.cs | 30 +++++++-- ...actNavigateToSearchService.NormalSearch.cs | 67 +++++++------------ 2 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index c5f83e44d88c5..12f4abf655a51 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -37,7 +37,7 @@ internal abstract partial class AbstractNavigateToSearchService (PatternMatchKind.LowercaseSubstring, NavigateToMatchKind.Fuzzy), ]; - private static async Task SearchProjectInCurrentProcessAsync( + private static async ValueTask SearchProjectInCurrentProcessAsync( Project project, ImmutableArray priorityDocuments, Document? searchDocument, string pattern, IImmutableSet kinds, Action onItemFound, @@ -46,7 +46,6 @@ private static async Task SearchProjectInCurrentProcessAsync( { try { - await Task.Yield().ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) return; @@ -59,9 +58,6 @@ private static async Task SearchProjectInCurrentProcessAsync( 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, onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); @@ -72,6 +68,14 @@ private static async Task SearchProjectInCurrentProcessAsync( { await onProjectCompleted().ConfigureAwait(false); } + + IEnumerable GetOrderedDocuments() + { + // 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); + + } } private static async Task ProcessDocumentsAsync( @@ -102,6 +106,22 @@ private static async Task ProcessDocumentsAsync( await Task.WhenAll(tasks).ConfigureAwait(false); } + //async Task SearchDocumentsAsync( + // Action onItemFound) + //{ + // // If the user created a dotted pattern then we'll grab the last part of the name + // var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + + // var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + + // await Parallel.ForEachAsync( + // orderedDocuments, + // cancellationToken, + // (document, cancellationToken) => + // ProcessDocumentAsync( + // document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); + //} + private static async ValueTask ProcessDocumentAsync( Document document, string patternName, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index bb2bcd0a46931..a8ee4706f3303 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -115,69 +115,48 @@ public static async Task SearchProjectsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - ClearCachedData(); - - var orderedDocuments = GetOrderedDocuments(); - var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchDocumentsAsync), + FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchProjectsAsync), ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - IEnumerable GetOrderedDocuments() + async Task SearchProjectsAsync(Action onItemFound) { 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); - using var _3 = PooledHashSet.GetInstance(out var seenDocuments); - - - } - - async Task SearchDocumentsAsync( - IEnumerable orderedDocuments, - Action onItemFound) - { - // If the user created a dotted pattern then we'll grab the last part of the name - var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); - - var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); await Parallel.ForEachAsync( - orderedDocuments, + highPriProjects.Concat(lowPriProjects), cancellationToken, - (document, cancellationToken) => - ProcessDocumentAsync( - document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - } + (project, cancellationToken) => + SearchProjectInCurrentProcessAsync( + project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, + searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)).ConfigureAwait(false); - //async Task SearchProjectsAsync(Action onItemFound) - //{ - - // Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); - // await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); - // await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); - //} + + //await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); + //await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); + } - async Task ProcessProjectsAsync(HashSet projects, Action onItemFound) - { - using var _ = ArrayBuilder.GetInstance(out var tasks); + //async Task ProcessProjectsAsync(HashSet projects, Action onItemFound) + //{ + // using var _ = ArrayBuilder.GetInstance(out var tasks); - foreach (var project in projects) - { - if (cancellationToken.IsCancellationRequested) - return; + // foreach (var project in projects) + // { + // if (cancellationToken.IsCancellationRequested) + // return; - tasks.Add(SearchProjectInCurrentProcessAsync( - project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); - } + // tasks.Add(; + // } - await Task.WhenAll(tasks).ConfigureAwait(false); - } + // await Task.WhenAll(tasks).ConfigureAwait(false); + //} } } From fbeb9f7b0dce99081f48d75e360c1cde3160fb42 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 16:52:34 -0700 Subject: [PATCH 0891/1047] Simplify --- ...stractNavigateToSearchService.InProcess.cs | 84 ++++++++++++------- ...actNavigateToSearchService.NormalSearch.cs | 2 - 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 12f4abf655a51..5d0d6764c9bb4 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -20,6 +20,12 @@ namespace Microsoft.CodeAnalysis.NavigateTo; +#if NET +using Parallel = System.Threading.Tasks.Parallel; +#else +using Parallel = Roslyn.Utilities.Parallel; +#endif + internal abstract partial class AbstractNavigateToSearchService { private static readonly ImmutableArray<(PatternMatchKind roslynKind, NavigateToMatchKind vsKind)> s_kindPairs = @@ -44,6 +50,8 @@ private static async ValueTask SearchProjectInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { + Contract.ThrowIfTrue(priorityDocuments.Any(d => d.Project != project)); + try { if (cancellationToken.IsCancellationRequested) @@ -58,11 +66,16 @@ private static async ValueTask SearchProjectInCurrentProcessAsync( var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + await Parallel.ForEachAsync( + GetOrderedDocuments(), + cancellationToken, + (document, cancellationToken) => ProcessDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); + //await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); - // Then process non-priority documents. - await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + //// Then process non-priority documents. + //await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); } finally { @@ -71,40 +84,55 @@ private static async ValueTask SearchProjectInCurrentProcessAsync( IEnumerable GetOrderedDocuments() { - // 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); + // If we're filtering down to a specific document, then only search that. + if (searchDocument != null) + { + yield return searchDocument; + yield break; + } + + using var _1 = GetPooledHashSet(priorityDocuments, out var highPriDocs); + + // First the high pri docs. + foreach (var document in highPriDocs) + yield return document; + // The rest of the docs in the project. + foreach (var document in project.Documents) + { + if (!highPriDocs.Contains(document)) + yield return document; + } } } - private static async Task ProcessDocumentsAsync( - Document? searchDocument, - string patternName, - string? patternContainer, - DeclaredSymbolInfoKindSet kinds, - Action onItemFound, - HashSet documents, - CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return; + //private static async Task ProcessDocumentsAsync( + // Document? searchDocument, + // string patternName, + // string? patternContainer, + // DeclaredSymbolInfoKindSet kinds, + // Action onItemFound, + // HashSet documents, + // CancellationToken cancellationToken) + //{ + // if (cancellationToken.IsCancellationRequested) + // return; - using var _ = ArrayBuilder.GetInstance(out var tasks); + // using var _ = ArrayBuilder.GetInstance(out var tasks); - foreach (var document in documents) - { - if (searchDocument != null && searchDocument != document) - continue; + // foreach (var document in documents) + // { + // if (searchDocument != null && searchDocument != document) + // continue; - if (cancellationToken.IsCancellationRequested) - return; + // if (cancellationToken.IsCancellationRequested) + // return; - tasks.Add(ProcessDocumentAsync(document, patternName, patternContainer, kinds, onItemFound, cancellationToken)); - } + // tasks.Add(); + // } - await Task.WhenAll(tasks).ConfigureAwait(false); - } + // await Task.WhenAll(tasks).ConfigureAwait(false); + //} //async Task SearchDocumentsAsync( // Action onItemFound) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index a8ee4706f3303..5e15279d97d1a 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -25,7 +25,6 @@ namespace Microsoft.CodeAnalysis.NavigateTo; internal abstract partial class AbstractNavigateToSearchService { - public async Task SearchDocumentAsync( Document document, string searchPattern, @@ -138,7 +137,6 @@ await Parallel.ForEachAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)).ConfigureAwait(false); - //await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); //await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); From 950d652e9e88894e7181a155f2d59803db7caabe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 16:58:43 -0700 Subject: [PATCH 0892/1047] Simplify --- ...ToSearchService.GeneratedDocumentSearch.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 406f2bb4ba978..6668d2a1fd805 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -14,6 +14,12 @@ namespace Microsoft.CodeAnalysis.NavigateTo; +#if NET +using Parallel = System.Threading.Tasks.Parallel; +#else +using Parallel = Roslyn.Utilities.Parallel; +#endif + internal abstract partial class AbstractNavigateToSearchService { public async Task SearchGeneratedDocumentsAsync( @@ -81,22 +87,23 @@ async Task SearchProjectsAsync(Action onItemFound) 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) - { - if (cancellationToken.IsCancellationRequested) - return; - - // 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 onProjectCompleted().ConfigureAwait(false); - } + await Parallel.ForEachAsync( + projects, + cancellationToken, + async (project, cancellationToken) => + { + // 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 Parallel.ForEachAsync( + sourceGeneratedDocs, + cancellationToken, + (document, cancellationToken) => ProcessDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); + + await onProjectCompleted().ConfigureAwait(false); + }).ConfigureAwait(false); } } } From 0b6483b67522f0d7fd9830a29934c53d1fc94d07 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 16:59:29 -0700 Subject: [PATCH 0893/1047] Simplify --- ...actNavigateToSearchService.NormalSearch.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 5e15279d97d1a..03d1caa0a40f7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -3,15 +3,12 @@ // 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.Channels; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Roslyn.Utilities; @@ -136,25 +133,6 @@ await Parallel.ForEachAsync( SearchProjectInCurrentProcessAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)).ConfigureAwait(false); - - - //await ProcessProjectsAsync(highPriProjects, onItemFound).ConfigureAwait(false); - //await ProcessProjectsAsync(lowPriProjects, onItemFound).ConfigureAwait(false); } - - //async Task ProcessProjectsAsync(HashSet projects, Action onItemFound) - //{ - // using var _ = ArrayBuilder.GetInstance(out var tasks); - - // foreach (var project in projects) - // { - // if (cancellationToken.IsCancellationRequested) - // return; - - // tasks.Add(; - // } - - // await Task.WhenAll(tasks).ConfigureAwait(false); - //} } } From d484eef953c6ee3a855adda186899ad16ec5890a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:01:24 -0700 Subject: [PATCH 0894/1047] in progress --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 3 --- .../NavigateTo/AbstractNavigateToSearchService.InProcess.cs | 5 ----- .../Portable/NavigateTo/AbstractNavigateToSearchService.cs | 1 + 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 3a9fdcf64ae2e..ac2b24352e628 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -143,8 +143,6 @@ await Task.WhenAll( async Task ProcessBothProjectGroupsAsync(Action onItemFound) { - await Task.Yield().ConfigureAwait(false); - await ProcessProjectGroupsAsync(highPriorityGroups, onItemFound).ConfigureAwait(false); await ProcessProjectGroupsAsync(lowPriorityGroups, onItemFound).ConfigureAwait(false); } @@ -167,7 +165,6 @@ async Task ProcessProjectGroupAsync(IGrouping group, Ac if (cancellationToken.IsCancellationRequested) return; - await Task.Yield().ConfigureAwait(false); var project = group.Key; // Break the project into high-pri docs and low pri docs. diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 5d0d6764c9bb4..6feae0bf160d2 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -71,11 +71,6 @@ await Parallel.ForEachAsync( cancellationToken, (document, cancellationToken) => ProcessDocumentAsync( document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - - //await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, highPriDocs, cancellationToken).ConfigureAwait(false); - - //// Then process non-priority documents. - //await ProcessDocumentsAsync(searchDocument, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); } finally { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index b4858113e1ea7..1ea56d809c5ae 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -91,6 +91,7 @@ private static async Task FindAllItemsAndWriteToChannelAsync( Exception? exception = null; try { + await Task.Yield().ConfigureAwait(false); await findWorker(item => channelWriter.TryWrite(item)).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) From f046fb9ebdd881fd930c397363ce96089772229b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:08:53 -0700 Subject: [PATCH 0895/1047] in progress --- ...ateToSearchService.CachedDocumentSearch.cs | 85 ++++++------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index ac2b24352e628..b30dd934071ce 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,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Storage; @@ -24,6 +21,12 @@ namespace Microsoft.CodeAnalysis.NavigateTo; +#if NET +using Parallel = System.Threading.Tasks.Parallel; +#else +using Parallel = Roslyn.Utilities.Parallel; +#endif + using CachedIndexMap = ConcurrentDictionary<(IChecksummedPersistentStorageService service, DocumentKey documentKey, StringTable stringTable), AsyncLazy>; internal abstract partial class AbstractNavigateToSearchService @@ -136,31 +139,24 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, ProcessBothProjectGroupsAsync), + FindAllItemsAndWriteToChannelAsync(channel.Writer, ProcessAllProjectGroupsAsync), ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - async Task ProcessBothProjectGroupsAsync(Action onItemFound) + async Task ProcessAllProjectGroupsAsync(Action onItemFound) { - await ProcessProjectGroupsAsync(highPriorityGroups, onItemFound).ConfigureAwait(false); - await ProcessProjectGroupsAsync(lowPriorityGroups, onItemFound).ConfigureAwait(false); + await Parallel.ForEachAsync( + highPriorityGroups.Concat(lowPriorityGroups), + cancellationToken, + (group, cancellationToken) => + ProcessProjectGroupAsync(group, onItemFound, cancellationToken)).ConfigureAwait(false); } - async Task ProcessProjectGroupsAsync(HashSet> groups, Action onItemFound) - { - if (cancellationToken.IsCancellationRequested) - return; - - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var group in groups) - tasks.Add(ProcessProjectGroupAsync(group, onItemFound)); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - async Task ProcessProjectGroupAsync(IGrouping group, Action onItemFound) + async ValueTask ProcessProjectGroupAsync( + IGrouping group, + Action onItemFound, + CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return; @@ -171,49 +167,24 @@ async Task ProcessProjectGroupAsync(IGrouping group, Ac 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 Parallel.ForEachAsync( + highPriDocs.Concat(lowPriDocs), + cancellationToken, + async (documentKey, cancellationToken) => + { + var index = await GetIndexAsync(storageService, documentKey, cancellationToken).ConfigureAwait(false); + if (index == null) + return; - await SearchCachedDocumentsInCurrentProcessAsync( - storageService, patternName, patternContainer, declaredSymbolInfoKindsSet, - onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false); + ProcessIndex( + documentKey, document: null, patternName, patternContainer, declaredSymbolInfoKindsSet, onItemFound, index, 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, - Action onItemFound, - HashSet documentKeys, - CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return; - - 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; - - ProcessIndex( - documentKey, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - private static Task GetIndexAsync( IChecksummedPersistentStorageService storageService, DocumentKey documentKey, From 1973e1b819cdecb051acd261590231f5a46acd5d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:16:14 -0700 Subject: [PATCH 0896/1047] Pull out async --- ...stractNavigateToSearchService.InProcess.cs | 80 ++++++------------- 1 file changed, 23 insertions(+), 57 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 6feae0bf160d2..67e817bf85b1c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -101,50 +101,6 @@ IEnumerable GetOrderedDocuments() } } - //private static async Task ProcessDocumentsAsync( - // Document? searchDocument, - // string patternName, - // string? patternContainer, - // DeclaredSymbolInfoKindSet kinds, - // Action onItemFound, - // HashSet documents, - // CancellationToken cancellationToken) - //{ - // if (cancellationToken.IsCancellationRequested) - // return; - - // using var _ = ArrayBuilder.GetInstance(out var tasks); - - // foreach (var document in documents) - // { - // if (searchDocument != null && searchDocument != document) - // continue; - - // if (cancellationToken.IsCancellationRequested) - // return; - - // tasks.Add(); - // } - - // await Task.WhenAll(tasks).ConfigureAwait(false); - //} - - //async Task SearchDocumentsAsync( - // Action onItemFound) - //{ - // // If the user created a dotted pattern then we'll grab the last part of the name - // var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); - - // var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - - // await Parallel.ForEachAsync( - // orderedDocuments, - // cancellationToken, - // (document, cancellationToken) => - // ProcessDocumentAsync( - // document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - //} - private static async ValueTask ProcessDocumentAsync( Document document, string patternName, @@ -157,9 +113,18 @@ private static async ValueTask ProcessDocumentAsync( return; var index = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + using var _ = ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>.GetInstance(out var linkedIndices); + + foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) + { + var linkedDocument = document.Project.Solution.GetRequiredDocument(linkedDocumentId); + var linkedIndex = TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + linkedIndices.Add((linkedIndex, linkedDocumentId.ProjectId)); + } ProcessIndex( - DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, onItemFound, index, cancellationToken); + DocumentKey.ToDocumentKey(document), document, patternName, patternContainer, kinds, + index, linkedIndices, onItemFound, cancellationToken); } private static void ProcessIndex( @@ -168,8 +133,9 @@ private static void ProcessIndex( string patternName, string? patternContainer, DeclaredSymbolInfoKindSet kinds, - Action onItemFound, TopLevelSyntaxTreeIndex index, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices, + Action onItemFound, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -195,7 +161,8 @@ private static void ProcessIndex( documentKey, document, declaredSymbolInfo, nameMatcher, containerMatcher, - kinds, onItemFound, cancellationToken); + kinds, linkedIndices, + onItemFound, cancellationToken); } } @@ -206,6 +173,7 @@ private static void AddResultIfMatch( PatternMatcher nameMatcher, PatternMatcher? containerMatcher, DeclaredSymbolInfoKindSet kinds, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices, Action onItemFound, CancellationToken cancellationToken) { @@ -230,7 +198,8 @@ private static void AddResultIfMatch( // 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, cancellationToken); + var additionalMatchingProjects = GetAdditionalProjectsWithMatch( + document, declaredSymbolInfo, linkedIndices); var result = ConvertResult( documentKey, document, declaredSymbolInfo, nameMatches, containerMatches, additionalMatchingProjects); @@ -276,27 +245,24 @@ private static RoslynNavigateToItem ConvertResult( } private static ImmutableArray GetAdditionalProjectsWithMatch( - Document? document, DeclaredSymbolInfo declaredSymbolInfo, CancellationToken cancellationToken) + Document? document, + DeclaredSymbolInfo declaredSymbolInfo, + ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices) { if (document == null) return []; - var solution = document.Project.Solution; - var linkedDocumentIds = document.GetLinkedDocumentIds(); - if (linkedDocumentIds.Length == 0) + if (linkedIndices is null || linkedIndices.Count == 0) return []; using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var linkedDocumentId in linkedDocumentIds) + foreach (var (index, projectId) in linkedIndices) { - var linkedDocument = solution.GetRequiredDocument(linkedDocumentId); - var index = TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); - // 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(); From 87329d384ebb9f1e7720e84a13b7e70185251699 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:21:11 -0700 Subject: [PATCH 0897/1047] Simplify --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 5 +++-- .../NavigateTo/AbstractNavigateToSearchService.InProcess.cs | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index b30dd934071ce..812a829984f7b 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -163,7 +163,7 @@ async ValueTask ProcessProjectGroupAsync( var project = group.Key; - // Break the project into high-pri docs and low pri docs. + // Break the project into high-pri docs and low pri docs, and process in that order. using var _1 = GetPooledHashSet(group.Where(priorityDocumentKeysSet.Contains), out var highPriDocs); using var _2 = GetPooledHashSet(group.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); @@ -177,7 +177,8 @@ await Parallel.ForEachAsync( return; ProcessIndex( - documentKey, document: null, patternName, patternContainer, declaredSymbolInfoKindsSet, onItemFound, index, cancellationToken); + documentKey, document: null, patternName, patternContainer, declaredSymbolInfoKindsSet, + index, linkedIndices: null, onItemFound, cancellationToken); }).ConfigureAwait(false); // done with project. Let the host know. diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 67e817bf85b1c..e23f5e436c8c3 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -112,6 +112,8 @@ private static async ValueTask ProcessDocumentAsync( if (cancellationToken.IsCancellationRequested) return; + // 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); From bc0e3e68bf6306e81736b1612d70cc17a2b973f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:27:43 -0700 Subject: [PATCH 0898/1047] cleanup --- ...ToSearchService.GeneratedDocumentSearch.cs | 1 - ...stractNavigateToSearchService.InProcess.cs | 78 +++++++------------ ...actNavigateToSearchService.NormalSearch.cs | 2 + 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 6668d2a1fd805..a1eaaf31b9a6e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -94,7 +94,6 @@ await Parallel.ForEachAsync( { // 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 Parallel.ForEachAsync( sourceGeneratedDocs, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index e23f5e436c8c3..07bf6b24574a3 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -159,53 +159,31 @@ private static void ProcessIndex( if (declaredSymbolInfo.Kind == DeclaredSymbolInfoKind.Namespace) continue; - AddResultIfMatch( - documentKey, document, - declaredSymbolInfo, - nameMatcher, containerMatcher, - kinds, linkedIndices, - onItemFound, cancellationToken); - } - } - - private static void AddResultIfMatch( - DocumentKey documentKey, - Document? document, - DeclaredSymbolInfo declaredSymbolInfo, - PatternMatcher nameMatcher, - PatternMatcher? containerMatcher, - DeclaredSymbolInfoKindSet kinds, - ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices, - Action onItemFound, - CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return; - - using var nameMatches = TemporaryArray.Empty; - using var containerMatches = TemporaryArray.Empty; + using var nameMatches = TemporaryArray.Empty; + using var containerMatches = TemporaryArray.Empty; - 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); + 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); + } } } @@ -251,13 +229,10 @@ private static ImmutableArray GetAdditionalProjectsWithMatch( DeclaredSymbolInfo declaredSymbolInfo, ArrayBuilder<(TopLevelSyntaxTreeIndex, ProjectId)>? linkedIndices) { - if (document == null) - return []; - - if (linkedIndices is null || linkedIndices.Count == 0) + if (document == null || linkedIndices is null || linkedIndices.Count == 0) return []; - using var _ = ArrayBuilder.GetInstance(out var result); + using var result = TemporaryArray.Empty; foreach (var (index, projectId) in linkedIndices) { @@ -267,7 +242,6 @@ private static ImmutableArray GetAdditionalProjectsWithMatch( result.Add(projectId); } - result.RemoveDuplicates(); return result.ToImmutableAndClear(); } diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 03d1caa0a40f7..d5c9a6c20e68e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -126,6 +126,8 @@ async Task SearchProjectsAsync(Action onItemFound) Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + // 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 Parallel.ForEachAsync( highPriProjects.Concat(lowPriProjects), cancellationToken, From 3aec9994d8bae611c01db3c82a31e5e3a2e979ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:37:02 -0700 Subject: [PATCH 0899/1047] Simplify --- ...ateToSearchService.CachedDocumentSearch.cs | 2 +- ...ToSearchService.GeneratedDocumentSearch.cs | 2 +- ...stractNavigateToSearchService.InProcess.cs | 2 +- ...actNavigateToSearchService.NormalSearch.cs | 2 +- .../Portable/Utilities/Parallel.ForEach.cs | 472 +----------------- 5 files changed, 19 insertions(+), 461 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 812a829984f7b..36820282d3604 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo; #if NET using Parallel = System.Threading.Tasks.Parallel; #else -using Parallel = Roslyn.Utilities.Parallel; +using Parallel = Roslyn.Utilities.ParallelUtilities; #endif using CachedIndexMap = ConcurrentDictionary<(IChecksummedPersistentStorageService service, DocumentKey documentKey, StringTable stringTable), AsyncLazy>; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index a1eaaf31b9a6e..8b77d4452d8da 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo; #if NET using Parallel = System.Threading.Tasks.Parallel; #else -using Parallel = Roslyn.Utilities.Parallel; +using Parallel = Roslyn.Utilities.ParallelUtilities; #endif internal abstract partial class AbstractNavigateToSearchService diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 07bf6b24574a3..b5aae472021c7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo; #if NET using Parallel = System.Threading.Tasks.Parallel; #else -using Parallel = Roslyn.Utilities.Parallel; +using Parallel = Roslyn.Utilities.ParallelUtilities; #endif internal abstract partial class AbstractNavigateToSearchService diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index d5c9a6c20e68e..5243f1b23b700 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.NavigateTo; #if NET using Parallel = System.Threading.Tasks.Parallel; #else -using Parallel = Roslyn.Utilities.Parallel; +using Parallel = Roslyn.Utilities.ParallelUtilities; #endif internal abstract partial class AbstractNavigateToSearchService diff --git a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs index f8f59ea0bf858..947f31a4a423b 100644 --- a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs +++ b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs @@ -3,6 +3,8 @@ #if !NET +#pragma warning disable CA1068 // CancellationToken parameters must come last + using System; using System.Collections.Generic; using System.Diagnostics; @@ -10,472 +12,28 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; -#pragma warning disable CA1068 // CancellationToken parameters must come last -#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods +namespace Roslyn.Utilities; -namespace Roslyn.Utilities +internal static partial class ParallelUtilities { - internal static partial class Parallel + public static async Task ForEachAsync( + IEnumerable source, + CancellationToken cancellationToken, + Func body) { - public static Task ForEachAsync(IEnumerable source, Func body) - { - return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, default(CancellationToken), body); - } - - public static Task ForEachAsync(IEnumerable source, CancellationToken cancellationToken, Func body) - { - return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, cancellationToken, body); - } - - private static Task ForEachAsync(IEnumerable source, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) - { - Debug.Assert(source != null); - Debug.Assert(scheduler != null); - Debug.Assert(body != null); - - // One fast up-front check for cancellation before we start the whole operation. - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - // The worker body. Each worker will execute this same body. - Func taskBody = static async o => - { - var state = (SyncForEachAsyncState)o; - var launchedNext = false; - -#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current - try - { - // Continue to loop while there are more elements to be processed. - while (!state.Cancellation.IsCancellationRequested) - { - // Get the next element from the enumerator. This requires asynchronously locking around MoveNext/Current. - TSource element; - await state.AcquireLock(); - try - { - if (state.Cancellation.IsCancellationRequested || // check now that the lock has been acquired - !state.Enumerator.MoveNext()) - { - break; - } - - element = state.Enumerator.Current; - } - finally - { - state.ReleaseLock(); - } - - // If the remaining dop allows it and we've not yet queued the next worker, do so now. We wait - // until after we've grabbed an item from the enumerator to a) avoid unnecessary contention on the - // serialized resource, and b) avoid queueing another work if there aren't any more items. Each worker - // is responsible only for creating the next worker, which in turn means there can't be any contention - // on creating workers (though it's possible one worker could be executing while we're creating the next). - if (!launchedNext) - { - launchedNext = true; - state.QueueWorkerIfDopAvailable(); - } - - // Process the loop body. - await state.LoopBody(element, state.Cancellation.Token); - } - } - catch (Exception e) - { - // Record the failure and then don't let the exception propagate. The last worker to complete - // will propagate exceptions as is appropriate to the top-level task. - state.RecordException(e); - } - finally - { - // If we're the last worker to complete, clean up and complete the operation. - if (state.SignalWorkerCompletedIterating()) - { - try - { - state.Dispose(); - } - catch (Exception e) - { - state.RecordException(e); - } - - // Finally, complete the task returned to the ForEachAsync caller. - // This must be the very last thing done. - state.Complete(); - } - } -#pragma warning restore CA2007 - }; - - try - { - // Construct a state object that encapsulates all state to be passed and shared between - // the workers, and queues the first worker. - var state = new SyncForEachAsyncState(source, taskBody, dop, scheduler, cancellationToken, body); - state.QueueWorkerIfDopAvailable(); - return state.Task; - } - catch (Exception e) - { - return Task.FromException(e); - } - } - - public static Task ForEachAsync(IAsyncEnumerable source, Func body) - { - return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, default(CancellationToken), body); - } - - public static Task ForEachAsync(IAsyncEnumerable source, CancellationToken cancellationToken, Func body) - { - return ForEachAsync(source, DefaultDegreeOfParallelism, TaskScheduler.Default, cancellationToken, body); - } - - private static Task ForEachAsync(IAsyncEnumerable source, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) - { - Debug.Assert(source != null); - Debug.Assert(scheduler != null); - Debug.Assert(body != null); - - // One fast up-front check for cancellation before we start the whole operation. - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - // The worker body. Each worker will execute this same body. - Func taskBody = static async o => - { - var state = (AsyncForEachAsyncState)o; - var launchedNext = false; - -#pragma warning disable CA2007 // Explicitly don't use ConfigureAwait, as we want to perform all work on the specified scheduler that's now current - try - { - // Continue to loop while there are more elements to be processed. - while (!state.Cancellation.IsCancellationRequested) - { - // Get the next element from the enumerator. This requires asynchronously locking around MoveNextAsync/Current. - TSource element; - await state.AcquireLock(); - try - { - if (state.Cancellation.IsCancellationRequested || // check now that the lock has been acquired - !await state.Enumerator.MoveNextAsync()) - { - break; - } - - element = state.Enumerator.Current; - } - finally - { - state.ReleaseLock(); - } - - // If the remaining dop allows it and we've not yet queued the next worker, do so now. We wait - // until after we've grabbed an item from the enumerator to a) avoid unnecessary contention on the - // serialized resource, and b) avoid queueing another work if there aren't any more items. Each worker - // is responsible only for creating the next worker, which in turn means there can't be any contention - // on creating workers (though it's possible one worker could be executing while we're creating the next). - if (!launchedNext) - { - launchedNext = true; - state.QueueWorkerIfDopAvailable(); - } - - // Process the loop body. - await state.LoopBody(element, state.Cancellation.Token); - } - } - catch (Exception e) - { - // Record the failure and then don't let the exception propagate. The last worker to complete - // will propagate exceptions as is appropriate to the top-level task. - state.RecordException(e); - } - finally - { - // If we're the last worker to complete, clean up and complete the operation. - if (state.SignalWorkerCompletedIterating()) - { - try - { - await state.DisposeAsync(); - } - catch (Exception e) - { - state.RecordException(e); - } - - // Finally, complete the task returned to the ForEachAsync caller. - // This must be the very last thing done. - state.Complete(); - } - } -#pragma warning restore CA2007 - }; - - try - { - // Construct a state object that encapsulates all state to be passed and shared between - // the workers, and queues the first worker. - var state = new AsyncForEachAsyncState(source, taskBody, dop, scheduler, cancellationToken, body); - state.QueueWorkerIfDopAvailable(); - return state.Task; - } - catch (Exception e) - { - return Task.FromException(e); - } - } - - /// Gets the default degree of parallelism to use when none is explicitly provided. - private static int DefaultDegreeOfParallelism => Environment.ProcessorCount; + using var _ = ArrayBuilder.GetInstance(out var tasks); - /// Stores the state associated with a ForEachAsync operation, shared between all its workers. - /// Specifies the type of data being enumerated. - private abstract class ForEachAsyncState : TaskCompletionSource // , IThreadPoolWorkItem + foreach (var item in source) { - /// The caller-provided cancellation token. - private readonly CancellationToken _externalCancellationToken; - /// Registration with caller-provided cancellation token. - protected readonly CancellationTokenRegistration _registration; - /// - /// The delegate to invoke on each worker to run the enumerator processing loop. - /// - /// - /// This could have been an action rather than a func, but it returns a task so that the task body is an async Task - /// method rather than async void, even though the worker body catches all exceptions and the returned Task is ignored. - /// - private readonly Func _taskBody; - /// The on which all work should be performed. - private readonly TaskScheduler _scheduler; - /// The present at the time of the ForEachAsync invocation. This is only used if on the default scheduler. - private readonly ExecutionContext? _executionContext; - /// Semaphore used to provide exclusive access to the enumerator. - private readonly SemaphoreSlim? _lock; - - /// The number of outstanding workers. When this hits 0, the operation has completed. - private int _completionRefCount; - /// Any exceptions incurred during execution. - private List? _exceptions; - /// The number of workers that may still be created. - private int _remainingDop; - - /// The delegate to invoke for each element yielded by the enumerator. - public readonly Func LoopBody; - /// The internal token source used to cancel pending work. - public readonly CancellationTokenSource Cancellation = new CancellationTokenSource(); - - /// Initializes the state object. - protected ForEachAsyncState(Func taskBody, bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, Func body) - { - _taskBody = taskBody; - _lock = needsLock ? new SemaphoreSlim(initialCount: 1, maxCount: 1) : null; - _remainingDop = dop < 0 ? DefaultDegreeOfParallelism : dop; - LoopBody = body; - _scheduler = scheduler; - if (scheduler == TaskScheduler.Default) - { - _executionContext = ExecutionContext.Capture(); - } - - _externalCancellationToken = cancellationToken; - _registration = cancellationToken.Register(static o => ((ForEachAsyncState)o!).Cancellation.Cancel(), this); - } - - /// Queues another worker if allowed by the remaining degree of parallelism permitted. - /// This is not thread-safe and must only be invoked by one worker at a time. - public void QueueWorkerIfDopAvailable() - { - if (_remainingDop > 0) - { - _remainingDop--; - - // Queue the invocation of the worker/task body. Note that we explicitly do not pass a cancellation token here, - // as the task body is what's responsible for completing the ForEachAsync task, for decrementing the reference count - // on pending tasks, and for cleaning up state. If a token were passed to StartNew (which simply serves to stop the - // task from starting to execute if it hasn't yet by the time cancellation is requested), all of that logic could be - // skipped, and bad things could ensue, e.g. deadlocks, leaks, etc. Also note that we need to increment the pending - // work item ref count prior to queueing the worker in order to avoid race conditions that could lead to temporarily - // and erroneously bouncing at zero, which would trigger completion too early. - Interlocked.Increment(ref _completionRefCount); - if (_scheduler == TaskScheduler.Default) - { - // If the scheduler is the default, we can avoid the overhead of the StartNew Task by just queueing - // this state object as the work item. - ThreadPool.QueueUserWorkItem(s => ((ForEachAsyncState)s).Execute(), this); - } - else - { - // We're targeting a non-default TaskScheduler, so queue the task body to it. - System.Threading.Tasks.Task.Factory.StartNew(_taskBody!, this, default(CancellationToken), TaskCreationOptions.DenyChildAttach, _scheduler); - } - } - } - - /// Signals that the worker has completed iterating. - /// true if this is the last worker to complete iterating; otherwise, false. - public bool SignalWorkerCompletedIterating() => Interlocked.Decrement(ref _completionRefCount) == 0; - - /// Asynchronously acquires exclusive access to the enumerator. - public Task AcquireLock() - { - // We explicitly don't pass this.Cancellation to WaitAsync. Doing so adds overhead, and it isn't actually - // necessary. All of the operations that monitor the lock are part of the same ForEachAsync operation, and the Task - // returned from ForEachAsync can't complete until all of the constituent operations have completed, including whoever - // holds the lock while this worker is waiting on the lock. Thus, the lock will need to be released for the overall - // operation to complete. Passing the token would allow the overall operation to potentially complete a bit faster in - // the face of cancellation, in exchange for making it a bit slower / more overhead in the common case of cancellation - // not being requested. We want to optimize for the latter. This also then avoids an exception throw / catch when - // cancellation is requested. - Debug.Assert(_lock is not null, "Should only be invoked when _lock is non-null"); - return _lock.WaitAsync(CancellationToken.None); - } - - /// Relinquishes exclusive access to the enumerator. - public void ReleaseLock() + tasks.Add(Task.Run(async () => { - Debug.Assert(_lock is not null, "Should only be invoked when _lock is non-null"); - _lock.Release(); - } - - /// Stores an exception and triggers cancellation in order to alert all workers to stop as soon as possible. - /// The exception. - public void RecordException(Exception e) - { - // Store the exception. - lock (this) - { - (_exceptions ??= new List()).Add(e); - } - - // Trigger cancellation of all workers. If cancellation has already been triggered - // due to a previous exception occurring, this is a nop. - try - { - Cancellation.Cancel(); - } - catch (AggregateException ae) - { - // If cancellation callbacks erroneously throw exceptions, include those exceptions in the list. - lock (this) - { - _exceptions.AddRange(ae.InnerExceptions); - } - } - } - - /// Completes the ForEachAsync task based on the status of this state object. - public void Complete() - { - Debug.Assert(_completionRefCount == 0, $"Expected {nameof(_completionRefCount)} == 0, got {_completionRefCount}"); - - bool taskSet; - if (_externalCancellationToken.IsCancellationRequested) - { - // The externally provided token had cancellation requested. Assume that any exceptions - // then are due to that, and just cancel the resulting task. - taskSet = TrySetCanceled(_externalCancellationToken); - } - else if (_exceptions is null) - { - // Everything completed successfully. - Debug.Assert(!Cancellation.IsCancellationRequested); - taskSet = TrySetResult(default(VoidResult)); - } - else - { - // Fail the task with the resulting exceptions. The first should be the initial - // exception that triggered the operation to shut down. The others, if any, may - // include cancellation exceptions from other concurrent operations being canceled - // in response to the primary exception. - taskSet = TrySetException(_exceptions); - } - - Debug.Assert(taskSet, "Complete should only be called once."); - } - - /// Executes the task body using the captured when ForEachAsync was invoked. - public void Execute() - { - Debug.Assert(_scheduler == TaskScheduler.Default, $"Expected {nameof(_scheduler)} == TaskScheduler.Default, got {_scheduler}"); - - if (_executionContext is null) - { - _taskBody(this); - } - else - { - ExecutionContext.Run(_executionContext, static o => ((ForEachAsyncState)o!)._taskBody(o), this); - } - } + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); } - private sealed class SyncForEachAsyncState : ForEachAsyncState, IDisposable - { - public readonly IEnumerator Enumerator; - - public SyncForEachAsyncState( - IEnumerable source, Func taskBody, - int dop, TaskScheduler scheduler, CancellationToken cancellationToken, - Func body) - : base(taskBody, needsLock: true, dop, scheduler, cancellationToken, body) - { - Enumerator = source.GetEnumerator(); - } - - public void Dispose() - { - _registration.Dispose(); - Enumerator.Dispose(); - } - } - - private sealed class AsyncForEachAsyncState : ForEachAsyncState, IAsyncDisposable - { - public readonly IAsyncEnumerator Enumerator; - - public AsyncForEachAsyncState( - IAsyncEnumerable source, Func taskBody, - int dop, TaskScheduler scheduler, CancellationToken cancellationToken, - Func body) - : base(taskBody, needsLock: true, dop, scheduler, cancellationToken, body) - { - Enumerator = source.GetAsyncEnumerator(Cancellation.Token); - } - - public ValueTask DisposeAsync() - { - _registration.Dispose(); - return Enumerator.DisposeAsync(); - } - } - - private sealed class ForEachState : ForEachAsyncState, IDisposable - { - public T NextAvailable; - public readonly T ToExclusive; - - public ForEachState( - T fromExclusive, T toExclusive, Func taskBody, - bool needsLock, int dop, TaskScheduler scheduler, CancellationToken cancellationToken, - Func body) - : base(taskBody, needsLock, dop, scheduler, cancellationToken, body) - { - NextAvailable = fromExclusive; - ToExclusive = toExclusive; - } - - public void Dispose() => _registration.Dispose(); - } + await Task.WhenAll(tasks).ConfigureAwait(false); } } From b14f2f749916907a7d08b64a11857d213854bb19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:38:39 -0700 Subject: [PATCH 0900/1047] async --- .../NavigateTo/AbstractNavigateToSearchService.InProcess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index b5aae472021c7..d379cdb313cbc 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -120,7 +120,7 @@ private static async ValueTask ProcessDocumentAsync( foreach (var linkedDocumentId in document.GetLinkedDocumentIds()) { var linkedDocument = document.Project.Solution.GetRequiredDocument(linkedDocumentId); - var linkedIndex = TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); + var linkedIndex = await TopLevelSyntaxTreeIndex.GetRequiredIndexAsync(linkedDocument, cancellationToken).ConfigureAwait(false); linkedIndices.Add((linkedIndex, linkedDocumentId.ProjectId)); } From 962752f7284abd6ac8857be9bd2cc7fd8aa61947 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:40:57 -0700 Subject: [PATCH 0901/1047] simplify --- src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs index 947f31a4a423b..e54cbff76ae6f 100644 --- a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs +++ b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs @@ -7,9 +7,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Numerics; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; From c7c9f0cdc9137aebe002c1deb813c087323b98bc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:49:47 -0700 Subject: [PATCH 0902/1047] Simplify --- ...ateToSearchService.CachedDocumentSearch.cs | 11 ++---- ...ToSearchService.GeneratedDocumentSearch.cs | 39 ++++++++++--------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 36820282d3604..c43daaabfa64f 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -144,16 +144,13 @@ await Task.WhenAll( return; - async Task ProcessAllProjectGroupsAsync(Action onItemFound) - { - await Parallel.ForEachAsync( + Task ProcessAllProjectGroupsAsync(Action onItemFound) + => Parallel.ForEachAsync( highPriorityGroups.Concat(lowPriorityGroups), cancellationToken, - (group, cancellationToken) => - ProcessProjectGroupAsync(group, onItemFound, cancellationToken)).ConfigureAwait(false); - } + (group, cancellationToken) => ProcessSingleProjectGroupAsync(group, onItemFound, cancellationToken)); - async ValueTask ProcessProjectGroupAsync( + async ValueTask ProcessSingleProjectGroupAsync( IGrouping group, Action onItemFound, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 8b77d4452d8da..584d76a339427 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -73,36 +73,37 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( if (cancellationToken.IsCancellationRequested) return; + // 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); + var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchProjectsAsync), + FindAllItemsAndWriteToChannelAsync(channel.Writer, ProcessAllProjectsAsync), ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - async Task SearchProjectsAsync(Action onItemFound) + Task ProcessAllProjectsAsync(Action onItemFound) + => Parallel.ForEachAsync( + projects, + cancellationToken, + (project, cancellationToken) => ProcessSingleProjectAsync(project, onItemFound, cancellationToken)); + + async ValueTask ProcessSingleProjectAsync( + Project project, Action onItemFound, CancellationToken cancellationToken) { - // 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); + // 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); await Parallel.ForEachAsync( - projects, + sourceGeneratedDocs, cancellationToken, - async (project, cancellationToken) => - { - // 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); - - await Parallel.ForEachAsync( - sourceGeneratedDocs, - cancellationToken, - (document, cancellationToken) => ProcessDocumentAsync( - document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - - await onProjectCompleted().ConfigureAwait(false); - }).ConfigureAwait(false); + (document, cancellationToken) => ProcessDocumentAsync( + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); + + await onProjectCompleted().ConfigureAwait(false); } } } From 23a55e9b5cd3a9208e219aefbbb7f370eee9c049 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 17:51:02 -0700 Subject: [PATCH 0903/1047] Simplify --- ...actNavigateToSearchService.NormalSearch.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 5243f1b23b700..77c7bc4543a17 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -111,30 +111,28 @@ public static async Task SearchProjectsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { + 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); + + Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); + var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchProjectsAsync), + FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchAllProjectsAsync), ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); return; - async Task SearchProjectsAsync(Action onItemFound) - { - 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); - - Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); - - // 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 Parallel.ForEachAsync( + Task SearchAllProjectsAsync(Action onItemFound) + => Parallel.ForEachAsync( highPriProjects.Concat(lowPriProjects), cancellationToken, (project, cancellationToken) => + // 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. SearchProjectInCurrentProcessAsync( project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)).ConfigureAwait(false); - } + searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); } } From 33432716fec96f85d38e9538509b8bde89457805 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:04:10 -0700 Subject: [PATCH 0904/1047] Simplify --- ...ateToSearchService.CachedDocumentSearch.cs | 10 +---- ...ToSearchService.GeneratedDocumentSearch.cs | 10 +---- ...stractNavigateToSearchService.InProcess.cs | 8 +--- ...actNavigateToSearchService.NormalSearch.cs | 8 +--- .../AbstractNavigateToSearchService.cs | 24 ++++++++++++ .../Portable/Utilities/Parallel.ForEach.cs | 37 ------------------- 6 files changed, 30 insertions(+), 67 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index c43daaabfa64f..beec227b8cf5c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -21,12 +21,6 @@ namespace Microsoft.CodeAnalysis.NavigateTo; -#if NET -using Parallel = System.Threading.Tasks.Parallel; -#else -using Parallel = Roslyn.Utilities.ParallelUtilities; -#endif - using CachedIndexMap = ConcurrentDictionary<(IChecksummedPersistentStorageService service, DocumentKey documentKey, StringTable stringTable), AsyncLazy>; internal abstract partial class AbstractNavigateToSearchService @@ -145,7 +139,7 @@ await Task.WhenAll( return; Task ProcessAllProjectGroupsAsync(Action onItemFound) - => Parallel.ForEachAsync( + => ParallelForEachAsync( highPriorityGroups.Concat(lowPriorityGroups), cancellationToken, (group, cancellationToken) => ProcessSingleProjectGroupAsync(group, onItemFound, cancellationToken)); @@ -164,7 +158,7 @@ async ValueTask ProcessSingleProjectGroupAsync( 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 Parallel.ForEachAsync( + await ParallelForEachAsync( highPriDocs.Concat(lowPriDocs), cancellationToken, async (documentKey, cancellationToken) => diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 584d76a339427..639a99c27cc5c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -14,12 +14,6 @@ namespace Microsoft.CodeAnalysis.NavigateTo; -#if NET -using Parallel = System.Threading.Tasks.Parallel; -#else -using Parallel = Roslyn.Utilities.ParallelUtilities; -#endif - internal abstract partial class AbstractNavigateToSearchService { public async Task SearchGeneratedDocumentsAsync( @@ -86,7 +80,7 @@ await Task.WhenAll( return; Task ProcessAllProjectsAsync(Action onItemFound) - => Parallel.ForEachAsync( + => ParallelForEachAsync( projects, cancellationToken, (project, cancellationToken) => ProcessSingleProjectAsync(project, onItemFound, cancellationToken)); @@ -97,7 +91,7 @@ async ValueTask ProcessSingleProjectAsync( // 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); - await Parallel.ForEachAsync( + await ParallelForEachAsync( sourceGeneratedDocs, cancellationToken, (document, cancellationToken) => ProcessDocumentAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index d379cdb313cbc..a0a469eb56ea2 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -20,12 +20,6 @@ namespace Microsoft.CodeAnalysis.NavigateTo; -#if NET -using Parallel = System.Threading.Tasks.Parallel; -#else -using Parallel = Roslyn.Utilities.ParallelUtilities; -#endif - internal abstract partial class AbstractNavigateToSearchService { private static readonly ImmutableArray<(PatternMatchKind roslynKind, NavigateToMatchKind vsKind)> s_kindPairs = @@ -66,7 +60,7 @@ private static async ValueTask SearchProjectInCurrentProcessAsync( var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - await Parallel.ForEachAsync( + await ParallelForEachAsync( GetOrderedDocuments(), cancellationToken, (document, cancellationToken) => ProcessDocumentAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 77c7bc4543a17..b881eab1eb0a3 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -14,12 +14,6 @@ namespace Microsoft.CodeAnalysis.NavigateTo; -#if NET -using Parallel = System.Threading.Tasks.Parallel; -#else -using Parallel = Roslyn.Utilities.ParallelUtilities; -#endif - internal abstract partial class AbstractNavigateToSearchService { public async Task SearchDocumentAsync( @@ -125,7 +119,7 @@ await Task.WhenAll( return; Task SearchAllProjectsAsync(Action onItemFound) - => Parallel.ForEachAsync( + => ParallelForEachAsync( highPriProjects.Concat(lowPriProjects), cancellationToken, (project, cancellationToken) => diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 1ea56d809c5ae..ec94bf9d7bc1e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -105,4 +105,28 @@ private static async Task FindAllItemsAndWriteToChannelAsync( channelWriter.TryComplete(exception); } } + +#pragma warning disable CA1068 // CancellationToken parameters must come last + private static async Task ParallelForEachAsync( +#pragma warning restore CA1068 // CancellationToken parameters must come last + IEnumerable source, + CancellationToken cancellationToken, + Func body) + { +#if NET + await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + foreach (var item in source) + { + tasks.Add(Task.Run(async () => + { + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } } diff --git a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs b/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs deleted file mode 100644 index e54cbff76ae6f..0000000000000 --- a/src/Workspaces/Core/Portable/Utilities/Parallel.ForEach.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#if !NET - -#pragma warning disable CA1068 // CancellationToken parameters must come last - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.PooledObjects; - -namespace Roslyn.Utilities; - -internal static partial class ParallelUtilities -{ - public static async Task ForEachAsync( - IEnumerable source, - CancellationToken cancellationToken, - Func body) - { - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var item in source) - { - tasks.Add(Task.Run(async () => - { - await body(item, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - } -} - -#endif From 096eb10c7f0748ab453db578ebcde4b3845fbdc0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:04:57 -0700 Subject: [PATCH 0905/1047] Simplify --- ...bstractNavigateToSearchService.CachedDocumentSearch.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index beec227b8cf5c..b8304b35471c8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -115,6 +115,10 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( 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); + // Process the documents by project group. That way, when each project is done, we can // report that back to the host for progress. var groups = documentKeys.GroupBy(d => d.Project).ToImmutableArray(); @@ -126,10 +130,6 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( 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); - // 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); - var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( From 22d21017c4d3f58da58802490a3ea53720d48a5f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:11:21 -0700 Subject: [PATCH 0906/1047] Simplify --- ...ateToSearchService.CachedDocumentSearch.cs | 9 +-- ...ToSearchService.GeneratedDocumentSearch.cs | 9 +-- ...actNavigateToSearchService.NormalSearch.cs | 7 +- .../AbstractNavigateToSearchService.cs | 68 +++++++++++-------- 4 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index b8304b35471c8..aea1e164f5d9b 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -108,9 +108,6 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) - return; - // Quick abort if OOP is now fully loaded. if (!ShouldSearchCachedDocuments(out _, out _)) return; @@ -130,11 +127,7 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( 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); - var channel = Channel.CreateUnbounded(s_channelOptions); - - await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, ProcessAllProjectGroupsAsync), - ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); + await PerformSearchAsync(ProcessAllProjectGroupsAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 639a99c27cc5c..c1d963a57a365 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -64,18 +64,11 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) - return; - // 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); - var channel = Channel.CreateUnbounded(s_channelOptions); - - await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, ProcessAllProjectsAsync), - ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); + await PerformSearchAsync(ProcessAllProjectsAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index b881eab1eb0a3..1e53d7662a5ee 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -110,12 +110,7 @@ public static async Task SearchProjectsInCurrentProcessAsync( Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); - var channel = Channel.CreateUnbounded(s_channelOptions); - - await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, SearchAllProjectsAsync), - ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); - + await PerformSearchAsync(SearchAllProjectsAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; Task SearchAllProjectsAsync(Action onItemFound) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index ec94bf9d7bc1e..c73d319c9a878 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -66,43 +66,57 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr return disposer; } - private static async Task ReadItemsFromChannelAndReportToCallbackAsync( - ChannelReader channelReader, + private static async Task PerformSearchAsync( + Func, Task> searchAsync, Func, Task> onItemsFound, CancellationToken cancellationToken) { - await Task.Yield().ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var items); + var channel = Channel.CreateUnbounded(s_channelOptions); - while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - // Grab as many items as we can from the channel at once and report in a batch. - while (channelReader.TryRead(out var item)) - items.Add(item); + await Task.WhenAll( + FindAllItemsAndWriteToChannelAsync(channel.Writer, searchAsync), + ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); - await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); - } - } + return; - private static async Task FindAllItemsAndWriteToChannelAsync( - ChannelWriter channelWriter, - Func, Task> findWorker) - { - Exception? exception = null; - try + static async Task ReadItemsFromChannelAndReportToCallbackAsync( + ChannelReader channelReader, + Func, Task> onItemsFound, + CancellationToken cancellationToken) { await Task.Yield().ConfigureAwait(false); - await findWorker(item => channelWriter.TryWrite(item)).ConfigureAwait(false); - } - catch (Exception ex) when ((exception = ex) == null) - { - throw ExceptionUtilities.Unreachable(); + using var _ = ArrayBuilder.GetInstance(out var items); + + while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a batch. + while (channelReader.TryRead(out var item)) + items.Add(item); + + await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); + } } - finally + + static async Task FindAllItemsAndWriteToChannelAsync( + ChannelWriter channelWriter, + Func, Task> findWorker) { - // No matter what path we take (exceptional or non-exceptional), always complete the channel so the - // writing task knows it's done. - channelWriter.TryComplete(exception); + Exception? exception = null; + try + { + await Task.Yield().ConfigureAwait(false); + await findWorker(item => channelWriter.TryWrite(item)).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. + channelWriter.TryComplete(exception); + } } } From 7aad781b7bf922f8930566f1067243e2a793adc6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:23:00 -0700 Subject: [PATCH 0907/1047] Simplify --- ...ateToSearchService.CachedDocumentSearch.cs | 14 ++++------ .../AbstractNavigateToSearchService.cs | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index aea1e164f5d9b..983f655254421 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -123,20 +123,16 @@ 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). + // that don't), and process in that order. 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 PerformSearchAsync(ProcessAllProjectGroupsAsync, onItemsFound, cancellationToken).ConfigureAwait(false); - + await PerformParallelSearchAsync( + highPriorityGroups.Concat(lowPriorityGroups), + ProcessSingleProjectGroupAsync, + onItemsFound, cancellationToken).ConfigureAwait(false); return; - Task ProcessAllProjectGroupsAsync(Action onItemFound) - => ParallelForEachAsync( - highPriorityGroups.Concat(lowPriorityGroups), - cancellationToken, - (group, cancellationToken) => ProcessSingleProjectGroupAsync(group, onItemFound, cancellationToken)); - async ValueTask ProcessSingleProjectGroupAsync( IGrouping group, Action onItemFound, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index c73d319c9a878..eacb9a63d72f8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -66,6 +66,34 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr return disposer; } + private static Task PerformParallelSearchAsync( + IEnumerable items, + Func, CancellationToken, ValueTask> callback, + Func, Task> onItemsFound, + CancellationToken cancellationToken) + { + return PerformSearchAsync( + onItemFound => ParallelForEachAsync( + items, cancellationToken, + (item, cancellationToken) => callback(item, onItemFound, cancellationToken)), + onItemsFound, + cancellationToken); + } + +#if false + Task SearchAllProjectsAsync(Action onItemFound) + => ParallelForEachAsync( + highPriProjects.Concat(lowPriProjects), + cancellationToken, + (project, cancellationToken) => + // 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. + SearchProjectInCurrentProcessAsync( + project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, + searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); + +#endif + private static async Task PerformSearchAsync( Func, Task> searchAsync, Func, Task> onItemsFound, From 932b8e95051f1cedeaf08e3dfd61fddb4978af5c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:25:35 -0700 Subject: [PATCH 0908/1047] Simplify --- ...ToSearchService.GeneratedDocumentSearch.cs | 9 +------- ...actNavigateToSearchService.NormalSearch.cs | 22 ++++++++----------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index c1d963a57a365..3003af18d6708 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -68,16 +68,9 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - await PerformSearchAsync(ProcessAllProjectsAsync, onItemsFound, cancellationToken).ConfigureAwait(false); - + await PerformParallelSearchAsync(projects, ProcessSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; - Task ProcessAllProjectsAsync(Action onItemFound) - => ParallelForEachAsync( - projects, - cancellationToken, - (project, cancellationToken) => ProcessSingleProjectAsync(project, onItemFound, cancellationToken)); - async ValueTask ProcessSingleProjectAsync( Project project, Action onItemFound, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 1e53d7662a5ee..edbdf9561b948 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -110,18 +110,14 @@ public static async Task SearchProjectsInCurrentProcessAsync( Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); - await PerformSearchAsync(SearchAllProjectsAsync, onItemsFound, cancellationToken).ConfigureAwait(false); - return; - - Task SearchAllProjectsAsync(Action onItemFound) - => ParallelForEachAsync( - highPriProjects.Concat(lowPriProjects), - cancellationToken, - (project, cancellationToken) => - // 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. - SearchProjectInCurrentProcessAsync( - project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); + // 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( + highPriProjects.Concat(lowPriProjects), + (project, onItemFound, cancellationToken) => SearchProjectInCurrentProcessAsync( + project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, + searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken), + onItemsFound, + cancellationToken).ConfigureAwait(false); } } From b0f654df2f9540219653140e06d7df9d2a74db81 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:36:46 -0700 Subject: [PATCH 0909/1047] in progress --- ...ateToSearchService.CachedDocumentSearch.cs | 8 +-- ...ToSearchService.GeneratedDocumentSearch.cs | 3 +- .../AbstractNavigateToSearchService.cs | 64 ++++++------------- 3 files changed, 26 insertions(+), 49 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 983f655254421..bd75fabf6a353 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -122,15 +122,13 @@ 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), and process in that order. + // 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. 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 PerformParallelSearchAsync( - highPriorityGroups.Concat(lowPriorityGroups), - ProcessSingleProjectGroupAsync, - onItemsFound, cancellationToken).ConfigureAwait(false); + highPriorityGroups.Concat(lowPriorityGroups), ProcessSingleProjectGroupAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; async ValueTask ProcessSingleProjectGroupAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 3003af18d6708..5f52fc50250af 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -68,7 +68,8 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - await PerformParallelSearchAsync(projects, ProcessSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); + await PerformParallelSearchAsync( + projects, ProcessSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; async ValueTask ProcessSingleProjectAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index eacb9a63d72f8..516b2b6aed961 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -66,74 +66,48 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr return disposer; } - private static Task PerformParallelSearchAsync( + /// + /// 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 async Task PerformParallelSearchAsync( IEnumerable items, Func, CancellationToken, ValueTask> callback, Func, Task> onItemsFound, CancellationToken cancellationToken) { - return PerformSearchAsync( - onItemFound => ParallelForEachAsync( - items, cancellationToken, - (item, cancellationToken) => callback(item, onItemFound, cancellationToken)), - onItemsFound, - cancellationToken); - } - -#if false - Task SearchAllProjectsAsync(Action onItemFound) - => ParallelForEachAsync( - highPriProjects.Concat(lowPriProjects), - cancellationToken, - (project, cancellationToken) => - // 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. - SearchProjectInCurrentProcessAsync( - project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken)); - -#endif - - private static async Task PerformSearchAsync( - Func, Task> searchAsync, - Func, Task> onItemsFound, - CancellationToken cancellationToken) - { + // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. + // Concurrently, the reading task will grab items when available, and send them over to the host. var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(channel.Writer, searchAsync), - ReadItemsFromChannelAndReportToCallbackAsync(channel.Reader, onItemsFound, cancellationToken)).ConfigureAwait(false); + FindAllItemsAndWriteToChannelAsync(), + ReadItemsFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); - return; - - static async Task ReadItemsFromChannelAndReportToCallbackAsync( - ChannelReader channelReader, - Func, Task> onItemsFound, - CancellationToken cancellationToken) + async Task ReadItemsFromChannelAndReportToCallbackAsync() { await Task.Yield().ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(out var items); - while (await channelReader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { // Grab as many items as we can from the channel at once and report in a batch. - while (channelReader.TryRead(out var item)) + while (channel.Reader.TryRead(out var item)) items.Add(item); await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); } } - static async Task FindAllItemsAndWriteToChannelAsync( - ChannelWriter channelWriter, - Func, Task> findWorker) + async Task FindAllItemsAndWriteToChannelAsync() { Exception? exception = null; try { await Task.Yield().ConfigureAwait(false); - await findWorker(item => channelWriter.TryWrite(item)).ConfigureAwait(false); + await PerformSearchAsync(item => channel.Writer.TryWrite(item)).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { @@ -143,9 +117,13 @@ static async Task FindAllItemsAndWriteToChannelAsync( { // No matter what path we take (exceptional or non-exceptional), always complete the channel so the // writing task knows it's done. - channelWriter.TryComplete(exception); + channel.Writer.TryComplete(exception); } } + + Task PerformSearchAsync(Action onItemFound) + => ParallelForEachAsync( + items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound, cancellationToken)); } #pragma warning disable CA1068 // CancellationToken parameters must come last From 0c0253917dc8bb9a40519b4bdd2a95bcf1e7077b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:39:35 -0700 Subject: [PATCH 0910/1047] Simplify --- .../AbstractNavigateToSearchService.InProcess.cs | 4 ++-- .../AbstractNavigateToSearchService.NormalSearch.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index a0a469eb56ea2..a8d06c43752ad 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -63,7 +63,7 @@ private static async ValueTask SearchProjectInCurrentProcessAsync( await ParallelForEachAsync( GetOrderedDocuments(), cancellationToken, - (document, cancellationToken) => ProcessDocumentAsync( + (document, cancellationToken) => SearchSingleDocumentAsync( document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); } finally @@ -95,7 +95,7 @@ IEnumerable GetOrderedDocuments() } } - private static async ValueTask ProcessDocumentAsync( + private static async ValueTask SearchSingleDocumentAsync( Document document, string patternName, string? patternContainer, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index edbdf9561b948..92331b098644c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; using Roslyn.Utilities; @@ -43,9 +44,16 @@ await client.TryInvokeAsync( await SearchDocumentInCurrentProcessAsync(document, searchPattern, kinds, onItemsFound, cancellationToken).ConfigureAwait(false); } - public static async Task SearchDocumentInCurrentProcessAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) + public static async Task SearchDocumentInCurrentProcessAsync( + Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) { + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + var results = new ConcurrentSet(); + await SearchSingleDocumentAsync( + document, patternName, patternContainerOpt, kinds, t => results.Add(t), cancellationToken).ConfigureAwait(false); + await SearchProjectInCurrentProcessAsync( document.Project, priorityDocuments: [], document, searchPattern, kinds, t => results.Add(t), () => Task.CompletedTask, cancellationToken).ConfigureAwait(false); From 513ff895f693aa2a94f4b6a500f9d51e24f0045d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:40:12 -0700 Subject: [PATCH 0911/1047] Simplify --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 1 - .../AbstractNavigateToSearchService.GeneratedDocumentSearch.cs | 1 - .../NavigateTo/AbstractNavigateToSearchService.InProcess.cs | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index bd75fabf6a353..efa71b0850028 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -112,7 +112,6 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( 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); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 5f52fc50250af..2a1affe632e87 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -64,7 +64,6 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { - // 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); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index a8d06c43752ad..51cdf0d0fa080 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -55,9 +55,7 @@ private static async ValueTask SearchProjectInCurrentProcessAsync( // 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); await ParallelForEachAsync( From 9aa47422511f2a674f7abc3b82e5527605ac3599 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:41:05 -0700 Subject: [PATCH 0912/1047] Simplify --- .../AbstractNavigateToSearchService.InProcess.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 51cdf0d0fa080..5b039fdfc1ee4 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -39,7 +39,7 @@ internal abstract partial class AbstractNavigateToSearchService private static async ValueTask SearchProjectInCurrentProcessAsync( Project project, ImmutableArray priorityDocuments, - Document? searchDocument, string pattern, IImmutableSet kinds, + string pattern, IImmutableSet kinds, Action onItemFound, Func onProjectCompleted, CancellationToken cancellationToken) @@ -71,13 +71,6 @@ await ParallelForEachAsync( IEnumerable GetOrderedDocuments() { - // If we're filtering down to a specific document, then only search that. - if (searchDocument != null) - { - yield return searchDocument; - yield break; - } - using var _1 = GetPooledHashSet(priorityDocuments, out var highPriDocs); // First the high pri docs. From ad9827e1abf41fa2ec5043dbb1c2aa8fcff4b660 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:47:11 -0700 Subject: [PATCH 0913/1047] Simplify --- ...ToSearchService.GeneratedDocumentSearch.cs | 5 +- ...stractNavigateToSearchService.InProcess.cs | 49 ------------------- ...actNavigateToSearchService.NormalSearch.cs | 39 ++++++++++----- .../AbstractNavigateToSearchService.cs | 3 ++ 4 files changed, 32 insertions(+), 64 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 2a1affe632e87..0817bf479f47a 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -67,8 +67,7 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - await PerformParallelSearchAsync( - projects, ProcessSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); + await PerformParallelSearchAsync(projects, ProcessSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; async ValueTask ProcessSingleProjectAsync( @@ -80,7 +79,7 @@ async ValueTask ProcessSingleProjectAsync( await ParallelForEachAsync( sourceGeneratedDocs, cancellationToken, - (document, cancellationToken) => ProcessDocumentAsync( + (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 5b039fdfc1ee4..64a90f344cdfd 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -37,55 +37,6 @@ internal abstract partial class AbstractNavigateToSearchService (PatternMatchKind.LowercaseSubstring, NavigateToMatchKind.Fuzzy), ]; - private static async ValueTask SearchProjectInCurrentProcessAsync( - Project project, ImmutableArray priorityDocuments, - string pattern, IImmutableSet kinds, - Action onItemFound, - Func onProjectCompleted, - CancellationToken cancellationToken) - { - Contract.ThrowIfTrue(priorityDocuments.Any(d => d.Project != project)); - - try - { - if (cancellationToken.IsCancellationRequested) - return; - - // 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(); - - var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern); - var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - - await ParallelForEachAsync( - GetOrderedDocuments(), - cancellationToken, - (document, cancellationToken) => SearchSingleDocumentAsync( - document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); - } - finally - { - await onProjectCompleted().ConfigureAwait(false); - } - - IEnumerable GetOrderedDocuments() - { - using var _1 = GetPooledHashSet(priorityDocuments, out var highPriDocs); - - // First the high pri docs. - foreach (var document in highPriDocs) - yield return document; - - // The rest of the docs in the project. - foreach (var document in project.Documents) - { - if (!highPriDocs.Contains(document)) - yield return document; - } - } - } - private static async ValueTask SearchSingleDocumentAsync( Document document, string patternName, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 92331b098644c..83619509a398e 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; @@ -52,11 +51,7 @@ public static async Task SearchDocumentInCurrentProcessAsync( var results = new ConcurrentSet(); await SearchSingleDocumentAsync( - document, patternName, patternContainerOpt, kinds, t => results.Add(t), cancellationToken).ConfigureAwait(false); - - await SearchProjectInCurrentProcessAsync( - document.Project, priorityDocuments: [], document, searchPattern, kinds, - t => results.Add(t), () => Task.CompletedTask, cancellationToken).ConfigureAwait(false); + document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); if (results.Count > 0) await onItemsFound(results.ToImmutableArray()).ConfigureAwait(false); @@ -113,6 +108,13 @@ public static async Task SearchProjectsInCurrentProcessAsync( Func onProjectCompleted, CancellationToken cancellationToken) { + // 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(); + + var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); + var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); + 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); @@ -121,11 +123,24 @@ public static async Task SearchProjectsInCurrentProcessAsync( // 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( - highPriProjects.Concat(lowPriProjects), - (project, onItemFound, cancellationToken) => SearchProjectInCurrentProcessAsync( - project, priorityDocuments.WhereAsArray(d => d.Project == project), searchDocument: null, - searchPattern, kinds, onItemFound, onProjectCompleted, cancellationToken), - onItemsFound, - cancellationToken).ConfigureAwait(false); + highPriProjects.Concat(lowPriProjects), SearchSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); + return; + + async ValueTask SearchSingleProjectAsync( + Project project, + Action onItemFound, + CancellationToken cancellationToken) + { + using var _1 = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); + using var _2 = GetPooledHashSet(project.Documents.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); + + await ParallelForEachAsync( + highPriDocs.Concat(lowPriDocs), + 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.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 516b2b6aed961..dac9ce87f3119 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -133,6 +133,9 @@ private static async Task ParallelForEachAsync( CancellationToken cancellationToken, Func body) { + if (cancellationToken.IsCancellationRequested) + return; + #if NET await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); #else From c5046c12e5c6f38fa8e9eccbd81388b24159adb2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 18:49:05 -0700 Subject: [PATCH 0914/1047] move --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 2 -- .../AbstractNavigateToSearchService.GeneratedDocumentSearch.cs | 1 - .../Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs | 2 ++ 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index efa71b0850028..b38bb1ab1dcd0 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -38,8 +38,6 @@ internal abstract partial class AbstractNavigateToSearchService /// private static StringTable? s_stringTable = new(); - private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; - private static void ClearCachedData() { // Volatiles are technically not necessary due to automatic fencing of reference-type writes. However, diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 0817bf479f47a..e17ff05e12ab7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -6,7 +6,6 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index dac9ce87f3119..befdf5e15d8d3 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -15,6 +15,8 @@ namespace Microsoft.CodeAnalysis.NavigateTo; internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavigateToSearchService { + private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; + public static readonly IImmutableSet AllKinds = [ NavigateToItemKind.Class, NavigateToItemKind.Constant, From 267c8fed56f9d3b443d911465f18e31c3a7a2a51 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 26 Apr 2024 20:37:41 -0700 Subject: [PATCH 0915/1047] cleanup --- ...ctNavigateToSearchService.CachedDocumentSearch.cs | 1 - .../Core/Portable/NavigateTo/NavigateToSearcher.cs | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index b38bb1ab1dcd0..82fd9d1051400 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -9,7 +9,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index 2d50b244face1..21df05a617e82 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -438,7 +438,7 @@ private Task SearchCachedDocumentsAsync( parallel: true, orderedProjects, seenItems, - async (service, projects, onItemsFound, onProjectCompleted) => + async (service, projects, onResultsFound, onProjectCompleted) => { // if the language doesn't support searching cached docs, immediately transition the project to the // completed state. @@ -451,7 +451,7 @@ private Task SearchCachedDocumentsAsync( { await advancedService.SearchCachedDocumentsAsync( _solution, projects, GetPriorityDocuments(projects), _searchPattern, _kinds, _activeDocument, - onItemsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); + onResultsFound, onProjectCompleted, cancellationToken).ConfigureAwait(false); } }, cancellationToken); @@ -495,7 +495,7 @@ private Task SearchGeneratedDocumentsAsync( parallel: false, 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. @@ -507,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); @@ -527,10 +527,10 @@ public IImmutableSet KindsProvided public bool CanFilter => false; - public Task SearchDocumentAsync(Document document, string searchPattern, IImmutableSet kinds, Func, Task> 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, Task> 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); From 391d97c17d0b1342e051c23541f8e5560db1ec56 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 09:08:31 -0700 Subject: [PATCH 0916/1047] simplify --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 82fd9d1051400..632437a8b8485 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -138,9 +138,8 @@ async ValueTask ProcessSingleProjectGroupAsync( var project = group.Key; // Break the project into high-pri docs and low pri docs, and process in that order. - using var _1 = GetPooledHashSet(group.Where(priorityDocumentKeysSet.Contains), out var highPriDocs); - using var _2 = GetPooledHashSet(group.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); - + var highPriDocs = group.Where(priorityDocumentKeysSet.Contains); + var lowPriDocs = group.Where(d => !highPriDocs.Contains(d)); await ParallelForEachAsync( highPriDocs.Concat(lowPriDocs), cancellationToken, From 43eca4d506f9e42e7470652e639d06041951286e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 09:14:50 -0700 Subject: [PATCH 0917/1047] simplify --- ...bstractNavigateToSearchService.CachedDocumentSearch.cs | 2 +- .../AbstractNavigateToSearchService.NormalSearch.cs | 8 ++++---- .../NavigateTo/AbstractNavigateToSearchService.cs | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 632437a8b8485..c56fa32757ba3 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -121,7 +121,7 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( // 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. 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); + var lowPriorityGroups = groups.Where(g => !highPriorityGroups.Contains(g)); await PerformParallelSearchAsync( highPriorityGroups.Concat(lowPriorityGroups), ProcessSingleProjectGroupAsync, onItemsFound, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 83619509a398e..344d02d7da730 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -115,8 +115,8 @@ public static async Task SearchProjectsInCurrentProcessAsync( var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); - 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); + using var _ = GetPooledHashSet(priorityDocuments.Select(d => d.Project), out var highPriProjects); + var lowPriProjects = projects.Where(p => !highPriProjects.Contains(p)); Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); @@ -131,8 +131,8 @@ async ValueTask SearchSingleProjectAsync( Action onItemFound, CancellationToken cancellationToken) { - using var _1 = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); - using var _2 = GetPooledHashSet(project.Documents.Where(d => !highPriDocs.Contains(d)), out var lowPriDocs); + using var _ = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); + var lowPriDocs = project.Documents.Where(d => !highPriDocs.Contains(d)); await ParallelForEachAsync( highPriDocs.Concat(lowPriDocs), diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index befdf5e15d8d3..59b46e655197f 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -68,6 +68,9 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr return disposer; } + //private static IEnumerable OrderByPriority(IEnumerable items, Func isPriority) + // => items.OrderBy((item1, item2) => (isPriority(item1) ? 0 : 1) - (isPriority(item2) ? 0 : 1)); + /// /// 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 From 21068802a8a61b82f0c59cc5fd4a241c98cfb6fc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 09:21:58 -0700 Subject: [PATCH 0918/1047] simplify --- ...igateToSearchService.CachedDocumentSearch.cs | 6 ++---- ...tractNavigateToSearchService.NormalSearch.cs | 9 +++------ .../AbstractNavigateToSearchService.cs | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index c56fa32757ba3..95513ef74c32a 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -120,11 +120,9 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( // 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. - using var _2 = GetPooledHashSet(groups.Where(g => g.Any(priorityDocumentKeysSet.Contains)), out var highPriorityGroups); - var lowPriorityGroups = groups.Where(g => !highPriorityGroups.Contains(g)); - await PerformParallelSearchAsync( - highPriorityGroups.Concat(lowPriorityGroups), ProcessSingleProjectGroupAsync, onItemsFound, cancellationToken).ConfigureAwait(false); + Prioritize(groups, g => g.Any(priorityDocumentKeysSet.Contains)), + ProcessSingleProjectGroupAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; async ValueTask ProcessSingleProjectGroupAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 344d02d7da730..0a6cd7a549ce5 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -116,14 +116,12 @@ public static async Task SearchProjectsInCurrentProcessAsync( var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); using var _ = GetPooledHashSet(priorityDocuments.Select(d => d.Project), out var highPriProjects); - var lowPriProjects = projects.Where(p => !highPriProjects.Contains(p)); - - Debug.Assert(projects.SetEquals(highPriProjects.Concat(lowPriProjects))); // 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( - highPriProjects.Concat(lowPriProjects), SearchSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); + Prioritize(projects, highPriProjects.Contains), + SearchSingleProjectAsync, onItemsFound, cancellationToken).ConfigureAwait(false); return; async ValueTask SearchSingleProjectAsync( @@ -132,10 +130,9 @@ async ValueTask SearchSingleProjectAsync( CancellationToken cancellationToken) { using var _ = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); - var lowPriDocs = project.Documents.Where(d => !highPriDocs.Contains(d)); await ParallelForEachAsync( - highPriDocs.Concat(lowPriDocs), + Prioritize(project.Documents, highPriDocs.Contains), cancellationToken, (document, cancellationToken) => SearchSingleDocumentAsync( document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onItemFound, cancellationToken)).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 59b46e655197f..3fc2865d93ed1 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -68,8 +68,21 @@ private static PooledDisposer> GetPooledHashSet(ImmutableArr return disposer; } - //private static IEnumerable OrderByPriority(IEnumerable items, Func isPriority) - // => items.OrderBy((item1, item2) => (isPriority(item1) ? 0 : 1) - (isPriority(item2) ? 0 : 1)); + 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 From d8d1b7884fb867591c12b35a2ac2b4df55b8deeb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 11:54:59 -0700 Subject: [PATCH 0919/1047] REmove unused part of FAR api --- .../CodeLens/CodeLensFindReferenceProgress.cs | 8 ---- ...stractFindUsagesService.ProgressAdapter.cs | 2 - .../ValueTracker.FindReferencesProgress.cs | 4 -- .../FindReferences/FindReferencesProgress.cs | 8 ---- .../FindReferencesSearchEngine.cs | 39 ++++++++----------- .../NoOpStreamingFindReferencesProgress.cs | 2 - .../StreamingFindReferencesProgress.cs | 12 ------ .../FindSymbols/IFindReferencesProgress.cs | 3 -- .../FindSymbols/IRemoteSymbolFinderService.cs | 2 - .../IStreamingFindReferencesProgress.cs | 3 -- .../FindSymbols/StreamingProgressCollector.cs | 3 -- .../SymbolFinder.CallbackDispatcher.cs | 6 --- ...mbolFinder.FindReferencesServerCallback.cs | 12 ------ .../SymbolFinder/RemoteSymbolFinderService.cs | 6 --- 14 files changed, 16 insertions(+), 94 deletions(-) diff --git a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs index 5a0a535ac915c..90336b1e720ec 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs @@ -58,14 +58,6 @@ public void OnCompleted() { } - public void OnFindInDocumentStarted(Document document) - { - } - - public void OnFindInDocumentCompleted(Document document) - { - } - private static bool FilterDefinition(ISymbol definition) { return definition.IsImplicitlyDeclared || diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 232b266014dab..0aa6d2d0e4d2b 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -73,8 +73,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 diff --git a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs index 91ae027a843b9..88803483a6103 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs @@ -28,10 +28,6 @@ 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 OnReferenceFoundAsync(SymbolGroup _, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) { if (!location.Location.IsInSource) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs index 747dc1dde863f..f781b6f4fb0ee 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs @@ -35,12 +35,4 @@ public void OnDefinitionFound(ISymbol symbol) public void OnReferenceFound(ISymbol symbol, ReferenceLocation location) { } - - public void OnFindInDocumentStarted(Document document) - { - } - - public void OnFindInDocumentCompleted(Document document) - { - } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index eb3a7e932959e..d3f459d74b09c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -260,34 +260,27 @@ private async Task ProcessDocumentAsync( Dictionary> symbolToGlobalAliases, CancellationToken cancellationToken) { - await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); + // 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); - 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); - - // scratch array to place results in. Populated/inspected/cleared in inner loop. - using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); - - foreach (var symbol in symbols) - { - var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(cache, globalAliases); + // scratch array to place results in. Populated/inspected/cleared in inner loop. + using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); - await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); - } - } - finally + foreach (var symbol in symbols) { - await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); + var globalAliases = TryGet(symbolToGlobalAliases, symbol); + var state = new FindReferencesDocumentState(cache, globalAliases); + + await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); } + return; + async Task ProcessDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, ArrayBuilder foundReferenceLocations) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 511fd7fb42fd7..0fd8e32d4fd51 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -27,8 +27,6 @@ private NoOpStreamingFindReferencesProgress() public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => default; public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentStartedAsync(Document document, CancellationToken cancellationToken) => default; - public ValueTask OnFindInDocumentCompletedAsync(Document document, CancellationToken cancellationToken) => default; private class NoOpProgressTracker : IStreamingProgressTracker { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index 0a13aa5929644..e1ad7254671c0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -37,18 +37,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 diff --git a/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs index af48d3cf3a972..f4e7520000deb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs @@ -13,9 +13,6 @@ public interface IFindReferencesProgress void OnStarted(); void OnCompleted(); - void OnFindInDocumentStarted(Document document); - void OnFindInDocumentCompleted(Document document); - void OnDefinitionFound(ISymbol symbol); void OnReferenceFound(ISymbol symbol, ReferenceLocation location); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 1f3b3736c5090..95955a12a9179 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -19,8 +19,6 @@ 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); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index fa7a34043bc62..9a17d6128d8e6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -72,9 +72,6 @@ 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); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 9018ef3297747..1bb38c80b9e03 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -51,9 +51,6 @@ public ImmutableArray GetReferencedSymbols() 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 diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index 1c4eaad54f7a0..6e209f2ed2efb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -43,12 +43,6 @@ 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); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 50b7dd3af8c58..27f9fece18c9b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -40,18 +40,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); diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index 43be04c3b336c..eeff70e2c5545 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -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); From ecd1f394560d580a7d8d24eebf1f89530f645299 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:04:33 -0700 Subject: [PATCH 0920/1047] parallel --- .../FindReferencesSearchEngine.cs | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index d3f459d74b09c..2fca16024a128 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; @@ -98,11 +99,25 @@ public async Task FindReferencesAsync( // 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); - using var _1 = ArrayBuilder.GetInstance(out var tasks); + await ParallelForEachAsync( + GetProjectsAndSymbolsToSearchAsync(symbolSet, projectsToSearch, cancellationToken), + cancellationToken, + async (tuple, cancellationToken) => + await ProcessProjectAsync(tuple.project, tuple.allSymbols, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + } + finally + { + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( + SymbolSet symbolSet, + ImmutableArray projectsToSearch, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var dependencyGraph = _solution.GetProjectDependencyGraph(); foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) { var currentProject = _solution.GetRequiredProject(projectId); @@ -114,21 +129,41 @@ public async Task FindReferencesAsync( // 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(); + var allSymbols = symbolSet.GetAllSymbols(); // Report any new symbols we've cascaded to to our caller. await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - tasks.Add(CreateWorkAsync(() => ProcessProjectAsync(currentProject, allSymbols, cancellationToken), cancellationToken)); + yield return (currentProject, allSymbols); } - - // Now, wait for all projects to complete. - await Task.WhenAll(tasks).ConfigureAwait(false); } - finally + } + +#pragma warning disable CA1068 // CancellationToken parameters must come last + private static async Task ParallelForEachAsync( +#pragma warning restore CA1068 // CancellationToken parameters must come last + IAsyncEnumerable source, + CancellationToken cancellationToken, + Func body) + { + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + await foreach (var item in source) { - await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + tasks.Add(Task.Run(async () => + { + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif } public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) From 94c066258dba4f5101b0aa1aa7fa25f6fe7827a2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:06:05 -0700 Subject: [PATCH 0921/1047] Docs --- .../FindReferences/FindReferencesSearchEngine.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 2fca16024a128..9efacc02c3984 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -101,11 +101,11 @@ public async Task FindReferencesAsync( // project. await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); + // Pull off and start searching each project as soon as we can once we've done the inheritance cascade into it. await ParallelForEachAsync( GetProjectsAndSymbolsToSearchAsync(symbolSet, projectsToSearch, cancellationToken), cancellationToken, - async (tuple, cancellationToken) => - await ProcessProjectAsync(tuple.project, tuple.allSymbols, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + ProcessProjectAsync).ConfigureAwait(false); } finally { @@ -219,8 +219,10 @@ 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) tuple, CancellationToken cancellationToken) { + var (project, allSymbols) = tuple; + using var _1 = PooledDictionary>.GetInstance(out var symbolToGlobalAliases); using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); try From 1042c6326f52fd15dbd1c5b0934c5d9d3359bca5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:19:33 -0700 Subject: [PATCH 0922/1047] in progrss --- .../FindReferencesSearchEngine.cs | 73 +++++++------------ .../Shared/Utilities/RoslynParallel.cs | 68 +++++++++++++++++ 2 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 9efacc02c3984..446a9c5e8a259 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -102,7 +102,7 @@ public async Task FindReferencesAsync( await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); // Pull off and start searching each project as soon as we can once we've done the inheritance cascade into it. - await ParallelForEachAsync( + await RoslynParallel.ForEachAsync( GetProjectsAndSymbolsToSearchAsync(symbolSet, projectsToSearch, cancellationToken), cancellationToken, ProcessProjectAsync).ConfigureAwait(false); @@ -139,33 +139,6 @@ await ParallelForEachAsync( } } -#pragma warning disable CA1068 // CancellationToken parameters must come last - private static async Task ParallelForEachAsync( -#pragma warning restore CA1068 // CancellationToken parameters must come last - IAsyncEnumerable source, - CancellationToken cancellationToken, - Func body) - { - if (cancellationToken.IsCancellationRequested) - return; - -#if NET - await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); -#else - using var _ = ArrayBuilder.GetInstance(out var tasks); - - await foreach (var item in source) - { - tasks.Add(Task.Run(async () => - { - await body(item, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); -#endif - } - public Task CreateWorkAsync(Func createWorkAsync, CancellationToken cancellationToken) => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap(); @@ -254,14 +227,14 @@ await finder.DetermineDocumentsToSearchAsync( } } - using var _4 = ArrayBuilder.GetInstance(out var tasks); - foreach (var (document, docSymbols) in documentToSymbols) - { - tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync( - document, docSymbols, symbolToGlobalAliases, cancellationToken), cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + documentToSymbols, + cancellationToken, + async (kvp, cancellationToken) => + { + var (document, docSymbols) = kvp; + await ProcessDocumentAsync(document, docSymbols, symbolToGlobalAliases, cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); } finally { @@ -305,21 +278,20 @@ private async Task ProcessDocumentAsync( // just grab those once here and hold onto them for the lifetime of this call. var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); - // scratch array to place results in. Populated/inspected/cleared in inner loop. - using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); - - foreach (var symbol in symbols) - { - var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(cache, globalAliases); + await RoslynParallel.ForEachAsync( + symbols, + cancellationToken, + async (symbol, cancellationToken) => + { + var globalAliases = TryGet(symbolToGlobalAliases, symbol); + var state = new FindReferencesDocumentState(cache, globalAliases); - await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); - } + await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); + }).ConfigureAwait(false); return; - async Task ProcessDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, ArrayBuilder foundReferenceLocations) + async Task ProcessDocumentAsync(ISymbol symbol, FindReferencesDocumentState state) { cancellationToken.ThrowIfCancellationRequested(); @@ -328,6 +300,13 @@ 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]; + + // scratch array to place results in. Populated/inspected/cleared in inner loop. + using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); + + // 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) { await finder.FindReferencesInDocumentAsync( 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..e0e5860e773de --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.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; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.Shared.Utilities; + +internal static class RoslynParallel +{ +#pragma warning disable CA1068 // CancellationToken parameters must come last + public static async Task ForEachAsync( +#pragma warning restore CA1068 // CancellationToken parameters must come last + IEnumerable source, + CancellationToken cancellationToken, + Func body) + { + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + foreach (var item in source) + { + tasks.Add(Task.Run(async () => + { + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } + +#pragma warning disable CA1068 // CancellationToken parameters must come last + public static async Task ForEachAsync( +#pragma warning restore CA1068 // CancellationToken parameters must come last + IAsyncEnumerable source, + CancellationToken cancellationToken, + Func body) + { + if (cancellationToken.IsCancellationRequested) + return; + +#if NET + await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); +#else + using var _ = ArrayBuilder.GetInstance(out var tasks); + + await foreach (var item in source) + { + tasks.Add(Task.Run(async () => + { + await body(item, cancellationToken).ConfigureAwait(false); + }, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); +#endif + } +} From 269b8a3c4202fb79209773b783311b8afffb7982 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:26:50 -0700 Subject: [PATCH 0923/1047] in progress --- .../FindReferencesSearchEngine.cs | 132 ++++++++++++------ .../IStreamingFindReferencesProgress.cs | 2 +- 2 files changed, 93 insertions(+), 41 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 446a9c5e8a259..a15cfb6b08af5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; @@ -19,9 +20,12 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +using Reference = (SymbolGroup group, ISymbol symbol, ReferenceLocation location); + internal partial class FindReferencesSearchEngine { private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); + private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; private readonly Solution _solution; private readonly IImmutableSet? _documents; @@ -72,44 +76,89 @@ public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationTo public async Task FindReferencesAsync( ImmutableArray symbols, CancellationToken cancellationToken) { - var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); - unifiedSymbols.AddRange(symbols); + var channel = Channel.CreateUnbounded(s_channelOptions); - await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); - try + await Task.WhenAll( + FindAllReferemcesAndWriteToChannelAsync(), + ReadReferencesFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + + async Task ReadReferencesFromChannelAndReportToCallbackAsync() { - var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposable.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); - - // Report the initial set of symbols to the caller. - var allSymbols = symbolSet.GetAllSymbols(); - await ReportGroupsAsync(allSymbols, cancellationToken).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); - - // 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. - await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); - - // 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), - cancellationToken, - ProcessProjectAsync).ConfigureAwait(false); + await Task.Yield().ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var references); + + while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a batch. + while (channel.Reader.TryRead(out var reference)) + references.Add(reference); + + await _progress.OnReferencesFoundAsync(references.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + } } - finally + + async Task FindAllReferemcesAndWriteToChannelAsync() { - await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + Exception? exception = null; + try + { + await Task.Yield().ConfigureAwait(false); + await PerformSearchAsync(item => channel.Writer.TryWrite(item)).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); + } + } + + async ValueTask PerformSearchAsync(Action onReferenceFound) + { + 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); + + // 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); + + // Report the initial set of symbols to the caller. + var allSymbols = symbolSet.GetAllSymbols(); + await ReportGroupsAsync(allSymbols, cancellationToken).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); + + // 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. + await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); + + // 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), + cancellationToken, + async (tuple, cancellationToken) => await ProcessProjectAsync( + tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + } + finally + { + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } } async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( @@ -192,10 +241,9 @@ private Task> GetProjectsToSearchAsync( return DependentProjectsFinder.GetDependentProjectsAsync(_solution, symbols, projects, cancellationToken); } - private async ValueTask ProcessProjectAsync((Project project, ImmutableArray allSymbols) tuple, CancellationToken cancellationToken) + private async ValueTask ProcessProjectAsync( + Project project, ImmutableArray allSymbols, Action onReferenceFound, CancellationToken cancellationToken) { - var (project, allSymbols) = tuple; - using var _1 = PooledDictionary>.GetInstance(out var symbolToGlobalAliases); using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); try @@ -233,7 +281,7 @@ await RoslynParallel.ForEachAsync( async (kvp, cancellationToken) => { var (document, docSymbols) = kvp; - await ProcessDocumentAsync(document, docSymbols, symbolToGlobalAliases, cancellationToken).ConfigureAwait(false); + await ProcessDocumentAsync(document, docSymbols, symbolToGlobalAliases, onReferenceFound, cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); } finally @@ -268,6 +316,7 @@ private async Task ProcessDocumentAsync( Document document, MetadataUnifyingSymbolHashSet symbols, Dictionary> symbolToGlobalAliases, + Action onReferenceFound, CancellationToken cancellationToken) { // We're doing to do all of our processing of this document at once. This will necessitate all the @@ -283,15 +332,18 @@ await RoslynParallel.ForEachAsync( cancellationToken, async (symbol, cancellationToken) => { + // symbolToGlobalAliases is safe to read in parallel. It is created fully before this point and is no + // longer mutated. var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState(cache, globalAliases); - await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); + await ProcessDocumentAsync(symbol, state, onReferenceFound).ConfigureAwait(false); }).ConfigureAwait(false); return; - async Task ProcessDocumentAsync(ISymbol symbol, FindReferencesDocumentState state) + async Task ProcessDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action onReferenceFound) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 9a17d6128d8e6..5a84f3f20655e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -73,7 +73,7 @@ internal interface IStreamingFindReferencesProgress ValueTask OnCompletedAsync(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 From cee978345f830361654e968e0e9b75aa3cd1d8f3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:29:30 -0700 Subject: [PATCH 0924/1047] No scratch buffer --- .../FindReferences/FindReferencesSearchEngine.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index a15cfb6b08af5..065ae6b065cef 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -353,20 +353,17 @@ async Task ProcessDocumentAsync( // happened. So there must be a group for this symbol in our map. var group = _symbolToGroup[symbol]; - // scratch array to place results in. Populated/inspected/cleared in inner loop. - using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); - // 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) { await finder.FindReferencesInDocumentAsync( - symbol, state, StandardCallbacks.AddToArrayBuilder, foundReferenceLocations, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in foundReferenceLocations) - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); - - foundReferenceLocations.Clear(); + symbol, state, + (loc, tuple) => tuple.onReferenceFound((tuple.group, tuple.symbol, loc.Location)), + (group, symbol, onReferenceFound), + _options, + cancellationToken).ConfigureAwait(false); } } } From 6e1adc1221237ef630925f84dcb9c1fd0341b90d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:33:56 -0700 Subject: [PATCH 0925/1047] Cache data up front --- .../FindSymbols/FindReferences/FindReferenceCache.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index aaad258c3e4e3..a073cc267cc11 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -50,11 +50,15 @@ static async Task ComputeCacheAsync(Document document, Cance private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; + public readonly ISyntaxFactsService SyntaxFacts; + private FindReferenceCache(Document document, SemanticModel semanticModel, SyntaxNode root) { Document = document; SemanticModel = semanticModel; Root = root; + SyntaxFacts = document.GetRequiredLanguageService(); + _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, @@ -112,7 +116,6 @@ public async ValueTask> FindMatchingIdentifierTokens static async ValueTask> ComputeAndCacheTokensAsync( FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) { - var syntaxFacts = document.GetRequiredLanguageService(); var root = await cache.SemanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); // If the identifier was escaped in the file then we'll have to do a more involved search that actually @@ -122,13 +125,13 @@ static async ValueTask> ComputeAndCacheTokensAsync( if (info.ProbablyContainsEscapedIdentifier(identifier)) { return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromTree(syntaxFacts, identifier, root)); + identifier, _ => FindMatchingIdentifierTokensFromTree(cache.SyntaxFacts, identifier, root)); } else { var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromText(syntaxFacts, identifier, root, text, cancellationToken)); + identifier, _ => FindMatchingIdentifierTokensFromText(cache.SyntaxFacts, identifier, root, text, cancellationToken)); } } From f17f7271d1653c43480c92ed852e36f9bacc4b3a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:34:36 -0700 Subject: [PATCH 0926/1047] simplify --- .../FindSymbols/FindReferences/FindReferenceCache.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index a073cc267cc11..4bf1857422ef4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -116,8 +116,6 @@ public async ValueTask> FindMatchingIdentifierTokens static async ValueTask> ComputeAndCacheTokensAsync( FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) { - 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. // @@ -125,13 +123,13 @@ static async ValueTask> ComputeAndCacheTokensAsync( if (info.ProbablyContainsEscapedIdentifier(identifier)) { return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromTree(cache.SyntaxFacts, identifier, root)); + identifier, _ => FindMatchingIdentifierTokensFromTree(cache.SyntaxFacts, identifier, cache.Root)); } else { var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromText(cache.SyntaxFacts, identifier, root, text, cancellationToken)); + identifier, _ => FindMatchingIdentifierTokensFromText(cache.SyntaxFacts, identifier, cache.Root, text, cancellationToken)); } } From 57c3ae67c0e144bd152f3f47346bea5ad676d873 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:41:04 -0700 Subject: [PATCH 0927/1047] Simplify --- .../FindReferences/FindReferenceCache.cs | 74 +++++++++---------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 4bf1857422ef4..a2e6ce8f723e5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -32,17 +32,27 @@ public static async ValueTask GetCacheAsync(Document documen 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); - return new(document, model, root); + + // 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); + + 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; @@ -50,13 +60,14 @@ static async Task ComputeCacheAsync(Document document, Cance private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; - public readonly ISyntaxFactsService SyntaxFacts; - - private FindReferenceCache(Document document, SemanticModel semanticModel, SyntaxNode root) + private FindReferenceCache( + Document document, SourceText text, SemanticModel semanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex) { Document = document; + Text = text; SemanticModel = semanticModel; Root = root; + SyntaxTreeIndex = syntaxTreeIndex; SyntaxFacts = document.GetRequiredLanguageService(); _identifierCache = new(comparer: semanticModel.Language switch @@ -103,50 +114,32 @@ 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, _ => FindMatchingIdentifierTokensFromTree()) + : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText()); - static async ValueTask> ComputeAndCacheTokensAsync( - FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) - { - // 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)) - { - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromTree(cache.SyntaxFacts, identifier, cache.Root)); - } - else - { - var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - return cache._identifierCache.GetOrAdd( - identifier, _ => FindMatchingIdentifierTokensFromText(cache.SyntaxFacts, identifier, cache.Root, text, cancellationToken)); - } - } - - static bool IsMatch(ISyntaxFactsService syntaxFacts, string identifier, SyntaxToken token) - => !token.IsMissing && syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, identifier); + bool IsMatch(string identifier, SyntaxToken token) + => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); - static ImmutableArray FindMatchingIdentifierTokensFromTree( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root) + ImmutableArray FindMatchingIdentifierTokensFromTree() { using var _ = ArrayBuilder.GetInstance(out var result); using var obj = SharedPools.Default>().GetPooledObject(); var stack = obj.Object; - stack.Push(root); + stack.Push(this.Root); while (stack.TryPop(out var current)) { + cancellationToken.ThrowIfCancellationRequested(); if (current.IsNode) { foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) @@ -155,7 +148,7 @@ static ImmutableArray FindMatchingIdentifierTokensFromTree( else if (current.IsToken) { var token = current.AsToken(); - if (IsMatch(syntaxFacts, identifier, token)) + if (IsMatch(identifier, token)) result.Add(token); if (token.HasStructuredTrivia) @@ -173,19 +166,18 @@ static ImmutableArray FindMatchingIdentifierTokensFromTree( return result.ToImmutableAndClear(); } - static ImmutableArray FindMatchingIdentifierTokensFromText( - ISyntaxFactsService syntaxFacts, string identifier, SyntaxNode root, SourceText sourceText, CancellationToken cancellationToken) + ImmutableArray FindMatchingIdentifierTokensFromText() { using var _ = ArrayBuilder.GetInstance(out var result); var index = 0; - while ((index = sourceText.IndexOf(identifier, index, syntaxFacts.IsCaseSensitive)) >= 0) + while ((index = this.Text.IndexOf(identifier, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) { cancellationToken.ThrowIfCancellationRequested(); - var token = root.FindToken(index, findInsideTrivia: true); + var token = this.Root.FindToken(index, findInsideTrivia: true); var span = token.Span; - if (span.Start == index && span.Length == identifier.Length && IsMatch(syntaxFacts, identifier, token)) + if (span.Start == index && span.Length == identifier.Length && IsMatch(identifier, token)) result.Add(token); var nextIndex = index + identifier.Length; From 2dedcbe446a13f20998225705298af357a80e0f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:43:37 -0700 Subject: [PATCH 0928/1047] IN progress --- .../FindSymbols/FindReferences/FindReferenceCache.cs | 6 ++---- .../FindReferences/NoOpStreamingFindReferencesProgress.cs | 3 ++- .../FindReferences/StreamingFindReferencesProgress.cs | 7 +++++-- .../Portable/FindSymbols/StreamingProgressCollector.cs | 8 +++++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index a2e6ce8f723e5..3239d6045d33a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -96,10 +96,8 @@ public SymbolInfo GetSymbolInfo(SyntaxNode node, CancellationToken cancellationT return null; } - public async ValueTask> FindMatchingIdentifierTokensAsync( - Document document, - string identifier, - CancellationToken cancellationToken) + public ImmutableArray FindMatchingIdentifierTokens( + string identifier, CancellationToken cancellationToken) { if (identifier == "") { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 0fd8e32d4fd51..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,7 +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 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/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index e1ad7254671c0..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; @@ -52,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/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 1bb38c80b9e03..912ae358bea4b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -69,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); } } From daa291e5969280bc2faf5ae24f013dffbd0caef0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:44:41 -0700 Subject: [PATCH 0929/1047] IN progress --- .../FindReferencesSearchEngine_FindReferencesInDocuments.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index cb31339e8022c..d7bae7de53f7b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -112,7 +112,7 @@ await finder.FindReferencesInDocumentAsync( foreach (var (_, location) in referencesForFinder) { var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + await _progress.OnReferencesFoundAsync([(group, symbol, location)], cancellationToken).ConfigureAwait(false); } referencesForFinder.Clear(); @@ -138,7 +138,7 @@ await finder.FindReferencesInDocumentAsync( 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); } } } From 3daa54fece4b963cb6cd3162836dab14eaaf8860 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:50:57 -0700 Subject: [PATCH 0930/1047] IN progress --- .../Finders/AbstractReferenceFinder.cs | 6 +-- .../FindSymbols/IRemoteSymbolFinderService.cs | 2 +- .../SymbolFinder.CallbackDispatcher.cs | 5 +- ...mbolFinder.FindReferencesServerCallback.cs | 46 ++++++++++--------- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 39b19e605551a..ab0d476c25074 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -171,12 +171,12 @@ protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync> 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( ISymbol symbol, diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 95955a12a9179..2c95215a67b28 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -20,7 +20,7 @@ internal interface ICallback ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, 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/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index 6e209f2ed2efb..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,8 +44,8 @@ public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId, Cancellati public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, CancellationToken cancellationToken) => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(symbolGroup, 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 27f9fece18c9b..edb253cc80fdc 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; @@ -67,33 +65,37 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated } public async ValueTask OnReferenceFoundAsync( - SerializableSymbolGroup serializableSymbolGroup, - SerializableSymbolAndProjectId serializableSymbol, - SerializableReferenceLocation reference, + 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); } } } From 1e351f537b4d2f7b180f0bc44318ddbec9cf0ced Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 12:57:39 -0700 Subject: [PATCH 0931/1047] Prime cache --- .../FindReferences/FindReferencesSearchEngine.cs | 13 +++++++++++++ ...erencesSearchEngine_FindReferencesInDocuments.cs | 3 +-- .../Finders/AbstractMemberScopedReferenceFinder.cs | 2 +- .../AbstractTypeParameterSymbolReferenceFinder.cs | 2 +- .../ConstructorInitializerSymbolReferenceFinder.cs | 5 +---- .../Finders/NamespaceSymbolReferenceFinder.cs | 3 +-- .../SymbolFinder.FindReferencesServerCallback.cs | 2 +- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 065ae6b065cef..5f2a538226dc2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -327,6 +327,19 @@ private async Task ProcessDocumentAsync( // just grab those once here and hold onto them for the lifetime of this call. var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); + // This search almost always involves trying to find the tokens matching the nname of the symbol we'er 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) + { + if (symbol.CanBeReferencedByName) + cache.FindMatchingIdentifierTokens(symbol.Name, cancellationToken); + } + await RoslynParallel.ForEachAsync( symbols, cancellationToken, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index d7bae7de53f7b..f8ae14197b86f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -123,8 +123,7 @@ await finder.FindReferencesInDocumentAsync( 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) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 574d7b6652cd9..4cb8f8581f371 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -64,7 +64,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( } else if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) { - var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index 38cc211cfb9ad..1fa78d29020b0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -29,7 +29,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( // 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); await FindReferencesInTokensAsync( symbol, state, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 4258a0eca666b..0de1898487e11 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -61,10 +61,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( { 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), diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index e08a522c68576..c8da137fdae10 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -107,8 +107,7 @@ private static async ValueTask AddNamedReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - var tokens = await FindMatchingIdentifierTokensAsync( - state, name, cancellationToken).ConfigureAwait(false); + var tokens = FindMatchingIdentifierTokens(state, name, cancellationToken); await FindReferencesInTokensAsync( symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index edb253cc80fdc..2830187083028 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -64,7 +64,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated await progress.OnDefinitionFoundAsync(symbolGroup, cancellationToken).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync( + public async ValueTask OnReferencesFoundAsync( ImmutableArray<(SerializableSymbolGroup serializableSymbolGroup, SerializableSymbolAndProjectId serializableSymbol, SerializableReferenceLocation reference)> references, CancellationToken cancellationToken) { From 4a6d2cbb7cb4158fc08017d4161ff6575c4de3b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:01:38 -0700 Subject: [PATCH 0932/1047] Simpliify --- .../Portable/FindSymbols/SymbolFinder_Helpers.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 76d59fc83456d..56079ea6e4df4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -146,7 +146,7 @@ private static async Task VerifyForwardedTypesAsync( CancellationToken cancellationToken) { 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 +155,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 (!await VerifyForwardedTypeAsync(solution, candidate: type1, forwardedTo: type2, cancellationToken).ConfigureAwait(false) && + !await VerifyForwardedTypeAsync(solution, candidate: type2, forwardedTo: type1, cancellationToken).ConfigureAwait(false)) { return false; } @@ -181,7 +178,6 @@ private static async Task VerifyForwardedTypeAsync( Solution solution, INamedTypeSymbol candidate, INamedTypeSymbol forwardedTo, - HashSet compilationSet, CancellationToken cancellationToken) { // Only need to operate on original definitions. i.e. List is the type that is forwarded, @@ -194,12 +190,6 @@ private static async Task VerifyForwardedTypeAsync( return false; var forwardedToCompilation = await forwardedToOriginatingProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - 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 From de5f7e528c6892d6d0ed63ca35d2af074f4500af Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:19:20 -0700 Subject: [PATCH 0933/1047] in progress --- .../FindSymbols/SymbolFinder_Helpers.cs | 6 ++-- .../Portable/Workspace/Solution/Solution.cs | 3 ++ ...tionCompilationState.CompilationTracker.cs | 8 +++-- ...eneratedFileReplacingCompilationTracker.cs | 13 ++++--- ...ionCompilationState.ICompilationTracker.cs | 5 ++- ...utionCompilationState.SymbolToProjectId.cs | 16 +++++---- ...utionCompilationState.UnrootedSymbolSet.cs | 36 +++++++++++++++---- 7 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 56079ea6e4df4..5ba1d075343e7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -185,12 +185,10 @@ private static async Task VerifyForwardedTypeAsync( candidate = GetOridinalUnderlyingType(candidate); forwardedTo = GetOridinalUnderlyingType(forwardedTo); - var forwardedToOriginatingProject = solution.GetOriginatingProject(forwardedTo); - if (forwardedToOriginatingProject == null) + var forwardedToCompilation = solution.GetOriginatingCompilation(forwardedTo); + if (forwardedToCompilation == null) return false; - var forwardedToCompilation = await forwardedToOriginatingProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var candidateFullMetadataName = candidate.ContainingNamespace?.IsGlobalNamespace != false ? candidate.MetadataName : $"{candidate.ContainingNamespace.ToDisplayString(SymbolDisplayFormats.SignatureFormat)}.{candidate.MetadataName}"; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 3d6f1098fbd04..ac898867a88cc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -194,6 +194,9 @@ private static Project CreateProject(ProjectId projectId, Solution solution) internal Project? GetOriginatingProject(ISymbol symbol) => GetProject(GetOriginatingProjectId(symbol)); + internal Compilation? GetOriginatingCompilation(ISymbol symbol) + => _compilationState.GetOriginatingProjectInfo(symbol)?.Compilation; + /// /// True if the solution contains the document in one of its projects /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index 11b1bd8884d08..07548d764dde5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -100,7 +100,10 @@ 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 @@ -112,11 +115,12 @@ SymbolKind.NetModule or { // 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 unrootedSymbolSet.Value.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out compilation, out referencedThrough); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 83dd3fa0a8ce6..65d261a1f7960 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 UnrootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic( + symbol, primary, out compilation, out referencedThrough); } public ICompilationTracker Fork(ProjectState newProject, TranslationAction? translate) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index 30455ec7cfd49..80ef8db9493ac 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -30,7 +30,10 @@ 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); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index 52a13061db566..4340fe789c135 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; @@ -175,10 +176,13 @@ internal partial class SolutionCompilationState else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && typeParameter.TypeParameterKind == TypeParameterKind.Cref) { - // 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); + Debug.Fail(""); + return null; + + //// 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); } 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.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs index 06837aab31a62..ae4707393ecb9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; @@ -27,10 +28,17 @@ internal static MetadataReferenceInfo From(MetadataReference reference) /// /// 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. + /// If the symbol is defined in a metadata reference of , information about the + /// reference. /// - internal sealed record class OriginatingProjectInfo(ProjectId ProjectId, MetadataReferenceInfo? ReferencedThrough); + internal sealed record class OriginatingProjectInfo( + ProjectId ProjectId, + Compilation Compilation, + MetadataReferenceInfo? ReferencedThrough); /// /// A helper type for mapping back to an originating . @@ -53,6 +61,8 @@ internal sealed record class OriginatingProjectInfo(ProjectId ProjectId, Metadat /// private readonly struct UnrootedSymbolSet { + public readonly Compilation Compilation; + /// /// The produced directly by . /// @@ -73,10 +83,12 @@ private readonly struct UnrootedSymbolSet public readonly ImmutableArray SecondaryReferencedSymbols; private UnrootedSymbolSet( + Compilation compilation, WeakReference primaryAssemblySymbol, WeakReference primaryDynamicSymbol, ImmutableArray secondaryReferencedSymbols) { + Compilation = compilation; PrimaryAssemblySymbol = primaryAssemblySymbol; PrimaryDynamicSymbol = primaryDynamicSymbol; SecondaryReferencedSymbols = secondaryReferencedSymbols; @@ -109,17 +121,24 @@ public static UnrootedSymbolSet Create(Compilation compilation) // 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()); + return new UnrootedSymbolSet(compilation, primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); } - 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) { referencedThrough = null; if (primary) { - return symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || - symbol.Equals(this.PrimaryDynamicSymbol.GetTarget()); + if (symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || + symbol.Equals(this.PrimaryDynamicSymbol.GetTarget())) + { + compilation = this.Compilation; + return true; + } } var secondarySymbols = this.SecondaryReferencedSymbols; @@ -130,7 +149,10 @@ public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out // the location we should start looking at. var index = secondarySymbols.BinarySearch(symbolHash, static (item, symbolHash) => item.hashCode.CompareTo(symbolHash)); if (index < 0) + { + compilation = null; 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. @@ -144,12 +166,14 @@ public bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary, out if (cached.symbol.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) { referencedThrough = cached.referenceInfo; + compilation = this.Compilation; return true; } index++; } + compilation = null; return false; } } From c8c6b37ab50ede4387ad4da52050713f79666618 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:27:37 -0700 Subject: [PATCH 0934/1047] Working --- ...stractFindUsagesService.ProgressAdapter.cs | 25 +++++++++++-------- .../ValueTracker.FindReferencesProgress.cs | 12 ++++++++- .../Portable/Workspace/Solution/Solution.cs | 5 ++++ .../SymbolFinder/RemoteSymbolFinderService.cs | 15 ++++++----- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 0aa6d2d0e4d2b..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; @@ -105,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/ValueTracking/ValueTracker.FindReferencesProgress.cs b/src/Features/Core/Portable/ValueTracking/ValueTracker.FindReferencesProgress.cs index 88803483a6103..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,7 +29,16 @@ private class FindReferencesProgress(OperationCollector valueTrackingProgressCol public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new(); - public async ValueTask OnReferenceFoundAsync(SymbolGroup _, ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) + 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); + } + + private async ValueTask OnReferenceFoundAsync( + ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken) { if (!location.Location.IsInSource) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index ac898867a88cc..df13aee1dcd6a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -194,6 +194,11 @@ 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; diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index eeff70e2c5545..83aef0424bc2b 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -225,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) From 99a8b9128f6381bb15905827bb920fb11c24844a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:36:32 -0700 Subject: [PATCH 0935/1047] in progress --- ...sSearchEngine_FindReferencesInDocuments.cs | 6 +-- .../Finders/AbstractReferenceFinder.cs | 23 +++++----- .../Finders/PropertySymbolReferenceFinder.cs | 35 ++++++++------- .../FindSymbols/SymbolFinder_Helpers.cs | 43 ++++++------------- .../FindSymbols/SymbolFinder_Hierarchy.cs | 8 ++-- 5 files changed, 47 insertions(+), 68 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index f8ae14197b86f..91852808ca728 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -179,7 +179,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 @@ -188,7 +188,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; } @@ -198,7 +198,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/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index ab0d476c25074..9ec64936b4916 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -36,7 +36,7 @@ public abstract Task DetermineDocumentsToSearchAsync( 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 FindReferencesInDocumentAsync( : 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); } @@ -178,7 +177,7 @@ protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync 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, @@ -193,12 +192,10 @@ protected static async ValueTask FindReferencesInTokensAsync( { 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); - processResult(finderLocation, processResultData); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index cb70c9670db68..d036995f4037c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -152,7 +152,7 @@ await FindReferencesInDocumentUsingSymbolNameAsync( await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (symbol.IsIndexer) - await FindIndexerReferencesAsync(symbol, state, processResult, processResultData, options, cancellationToken).ConfigureAwait(false); + FindIndexerReferences(symbol, state, processResult, processResultData, options, cancellationToken); await FindReferencesInDocumentInsideGlobalSuppressionsAsync( symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); @@ -172,7 +172,7 @@ private static Task FindDocumentWithIndexerMemberCrefAsync( project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } - private static async Task FindIndexerReferencesAsync( + private static void FindIndexerReferences( IPropertySymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -200,8 +200,7 @@ private static async Task FindIndexerReferencesAsync( { 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; @@ -217,7 +216,7 @@ private static async Task FindIndexerReferencesAsync( } } - 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, @@ -228,33 +227,33 @@ private static async Task FindIndexerReferencesAsync( 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 @@ -269,16 +268,16 @@ private static async Task FindIndexerReferencesAsync( 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 @@ -286,15 +285,15 @@ private static async Task FindIndexerReferencesAsync( 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/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 5ba1d075343e7..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,29 +117,19 @@ 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.Count == 0); @@ -160,8 +146,8 @@ private static async Task VerifyForwardedTypesAsync( // 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, cancellationToken).ConfigureAwait(false) && - !await VerifyForwardedTypeAsync(solution, candidate: type2, forwardedTo: type1, cancellationToken).ConfigureAwait(false)) + if (!VerifyForwardedType(solution, candidate: type1, forwardedTo: type2) && + !VerifyForwardedType(solution, candidate: type2, forwardedTo: type1)) { return false; } @@ -174,11 +160,10 @@ 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, - CancellationToken cancellationToken) + INamedTypeSymbol forwardedTo) { // Only need to operate on original definitions. i.e. List is the type that is forwarded, // not List. diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index 7bfe7ed73548e..0799a49bccd41 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; } From 6679c602a619a275b0cde5dca001aa79658a60a7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:44:16 -0700 Subject: [PATCH 0936/1047] in progress --- .../AbstractMemberScopedReferenceFinder.cs | 13 ++++--- .../Finders/AbstractReferenceFinder.cs | 38 ++++++++++--------- ...tractReferenceFinder_GlobalSuppressions.cs | 6 +-- ...tractTypeParameterSymbolReferenceFinder.cs | 8 ++-- ...tructorInitializerSymbolReferenceFinder.cs | 7 ++-- .../ConstructorSymbolReferenceFinder.cs | 14 +++---- ...ExplicitConversionSymbolReferenceFinder.cs | 3 +- .../Finders/FieldSymbolReferenceFinder.cs | 12 +++--- .../Finders/OperatorSymbolReferenceFinder.cs | 4 +- .../Finders/ParameterSymbolReferenceFinder.cs | 4 +- .../Finders/PropertySymbolReferenceFinder.cs | 6 +-- 11 files changed, 63 insertions(+), 52 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 4cb8f8581f371..bc902b6dde014 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -49,7 +49,7 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( TSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -60,13 +60,15 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( var container = GetContainer(symbol); if (container != null) { - await FindReferencesInContainerAsync(symbol, container, state, processResult, processResultData, 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); - await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } + + return ValueTaskFactory.CompletedTask; } private static ISymbol? GetContainer(ISymbol symbol) @@ -97,7 +99,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( return null; } - private ValueTask FindReferencesInContainerAsync( + private void FindReferencesInContainer( TSymbol symbol, ISymbol container, FindReferencesDocumentState state, @@ -121,6 +123,7 @@ private ValueTask FindReferencesInContainerAsync( } } - return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); + FindReferencesInTokens( + symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 9ec64936b4916..58919509f0b47 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -161,8 +161,7 @@ protected static Task FindDocumentsAsync( 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, @@ -171,7 +170,7 @@ protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync FindMatchingIdentifierTokens(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) @@ -368,7 +367,7 @@ protected static Task FindDocumentsWithForEachStatementsAsync(Project pro 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, @@ -377,7 +376,7 @@ protected static async Task FindReferencesInDocumentAsync( CancellationToken cancellationToken) { var document = state.Document; - var syntaxTreeInfo = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); + var syntaxTreeInfo = state.Cache.SyntaxTreeIndex; if (isRelevantDocument(syntaxTreeInfo)) { foreach (var node in state.Root.DescendantNodesAndSelf()) @@ -388,14 +387,15 @@ protected static async Task FindReferencesInDocumentAsync( } } - protected Task FindReferencesInForEachStatementsAsync( + protected void FindReferencesInForEachStatements( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; @@ -426,14 +426,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInCollectionInitializerAsync( + protected void FindReferencesInCollectionInitializer( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsCollectionInitializer; @@ -468,14 +469,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInDeconstructionAsync( + protected void FindReferencesInDeconstruction( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; @@ -505,14 +507,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInAwaitExpressionAsync( + protected void FindReferencesInAwaitExpression( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; @@ -535,14 +538,15 @@ void CollectMatchingReferences( } } - protected Task FindReferencesInImplicitObjectCreationExpressionAsync( + protected void FindReferencesInImplicitObjectCreationExpression( ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; @@ -902,10 +906,10 @@ protected virtual ValueTask> DetermineCascadedSymbolsAsy return new([]); } - protected static ValueTask FindReferencesInDocumentUsingSymbolNameAsync( + protected static void FindReferencesInDocumentUsingSymbolName( TSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } 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 13e1de1396ca0..e03b3805db4a8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -50,7 +50,7 @@ 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, @@ -61,7 +61,7 @@ protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppression 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; @@ -80,7 +80,7 @@ protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppression // 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); + var root = state.Root; foreach (var token in root.DescendantTokens()) { if (IsCandidate(state, token, expectedDocCommentId.Span, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken)) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index 1fa78d29020b0..b84e1bd7abaf1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -13,7 +13,7 @@ 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, @@ -31,19 +31,19 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( var tokens = FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); - await FindReferencesInTokensAsync( + FindReferencesInTokens( symbol, state, tokens.WhereAsArray(static (token, state) => !IsObjectCreationToken(token, state), state), processResult, processResultData, - cancellationToken).ConfigureAwait(false); + cancellationToken); GetObjectCreationReferences( tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state), processResult, processResultData); - return; + return ValueTaskFactory.CompletedTask; static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState state) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 0de1898487e11..3ee5b2f3a72e4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -51,7 +51,7 @@ protected override Task DetermineDocumentsToSearchAsync( }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action processResult, @@ -67,9 +67,8 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( static (token, tuple) => TokensMatch(tuple.state, token, tuple.methodSymbol.ContainingType.Name, tuple.cancellationToken), (state, methodSymbol, cancellationToken)); - await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); - - return; + 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 5037d55e9d1ad..64fa5b86ced25 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -118,14 +118,14 @@ await AddReferencesInDocumentWorkerAsync( // Finally, look for constructor references to predefined types (like `new int()`), // implicit object references, and inside global suppression attributes. - await FindPredefinedTypeReferencesAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindPredefinedTypeReferences( + methodSymbol, state, processResult, processResultData, cancellationToken); await FindReferencesInImplicitObjectCreationExpressionAsync( methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + methodSymbol, state, processResult, processResultData, cancellationToken); } /// @@ -158,7 +158,7 @@ private static ValueTask FindOrdinaryReferencesAsync( symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -167,7 +167,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( { var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return ValueTaskFactory.CompletedTask; + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -175,7 +175,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } private static ValueTask FindAttributeReferencesAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index 4078ddcbba83d..dfed302b16041 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -73,7 +73,8 @@ protected sealed override ValueTask FindReferencesInDocumentAsync( static (token, state) => IsPotentialReference(state.SyntaxFacts, token), state); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, 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/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 11c23a50ec558..4377ecd8bb5b5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -40,7 +41,7 @@ protected override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IFieldSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -48,9 +49,10 @@ protected override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + 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/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 5b3b92945dc16..37a2cc5d2a9df 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -62,8 +62,8 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( static (token, tuple) => IsPotentialReference(tuple.state.SyntaxFacts, tuple.op, token), (state, op)); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); await FindReferencesInDocumentInsideGlobalSuppressionsAsync( symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index b13c380c6f9c9..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; @@ -46,7 +47,8 @@ protected override ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentUsingIdentifier(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } protected override async ValueTask> DetermineCascadedSymbolsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index d036995f4037c..2fbc9bdf26257 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -149,13 +149,13 @@ await FindReferencesInDocumentUsingSymbolNameAsync( cancellationToken).ConfigureAwait(false); if (IsForEachProperty(symbol)) - await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); if (symbol.IsIndexer) FindIndexerReferences(symbol, state, processResult, processResultData, options, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); } private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( From cc563f7d439e0d893b3220e43b4aef444a5d97ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:47:02 -0700 Subject: [PATCH 0937/1047] in progress --- .../Finders/AbstractReferenceFinder.cs | 28 +++++++++---------- .../Finders/EventSymbolReferenceFinder.cs | 4 ++- .../Finders/NamespaceSymbolReferenceFinder.cs | 4 +-- .../Finders/OperatorSymbolReferenceFinder.cs | 7 +++-- .../Finders/PropertySymbolReferenceFinder.cs | 8 ++++-- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 58919509f0b47..b370fdfd0e747 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -239,7 +239,7 @@ public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentSt return null; } - protected static async Task FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, ISymbol symbol, FindReferencesDocumentState state, @@ -249,10 +249,10 @@ protected static async Task FindLocalAliasReferencesAsync( { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); if (!aliasSymbols.IsDefaultOrEmpty) - await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesThroughLocalAliasSymbols(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken); } - protected static async Task FindLocalAliasReferencesAsync( + protected static void FindLocalAliasReferences( ArrayBuilder initialReferences, FindReferencesDocumentState state, Action processResult, @@ -261,7 +261,7 @@ protected static async Task FindLocalAliasReferencesAsync( { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); if (!aliasSymbols.IsDefaultOrEmpty) - await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesThroughLocalAliasSymbols(state, aliasSymbols, processResult, processResultData, cancellationToken); } private static ImmutableArray GetLocalAliasSymbols( @@ -280,7 +280,7 @@ private static ImmutableArray GetLocalAliasSymbols( return aliasSymbols.ToImmutableAndClear(); } - private static async Task FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray localAliasSymbols, @@ -290,20 +290,20 @@ private static async Task FindReferencesThroughLocalAliasSymbolsAsync( { foreach (var localAliasSymbol in localAliasSymbols) { - await FindReferencesInDocumentUsingIdentifierAsync( - symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + 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)) { - await FindReferencesInDocumentUsingIdentifierAsync( - symbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + symbol, simpleName, state, processResult, processResultData, cancellationToken); } } } - private static async Task FindReferencesThroughLocalAliasSymbolsAsync( + private static void FindReferencesThroughLocalAliasSymbols( FindReferencesDocumentState state, ImmutableArray localAliasSymbols, Action processResult, @@ -312,15 +312,15 @@ private static async Task FindReferencesThroughLocalAliasSymbolsAsync( { foreach (var aliasSymbol in localAliasSymbols) { - await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + 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)) { - await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingIdentifier( + aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 8784ccd1b37ef..7b5870acdbde7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -56,6 +57,7 @@ protected sealed override ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, processResult, processResultData, cancellationToken); + FindReferencesInDocumentUsingSymbolName(symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index c8da137fdae10..229a035b6c08d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -90,8 +90,8 @@ await AddNamedReferencesAsync( await FindLocalAliasReferencesAsync( initialReferences, symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, 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 37a2cc5d2a9df..8605e1f4ef4b4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -47,7 +47,7 @@ private static Task FindDocumentsAsync( project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -64,8 +64,9 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesInTokens( symbol, state, tokens, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 2fbc9bdf26257..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; @@ -118,7 +119,7 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( private static bool IsForEachProperty(IPropertySymbol symbol) => symbol.Name == WellKnownMemberNames.CurrentPropertyName; - protected sealed override async ValueTask FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IPropertySymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -126,7 +127,7 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( + FindReferencesInDocumentUsingSymbolName( symbol, state, static (loc, data) => @@ -146,7 +147,7 @@ await FindReferencesInDocumentUsingSymbolNameAsync( data.processResult(loc, data.processResultData); }, processResultData: (self: this, symbol, state, processResult, processResultData, options, cancellationToken), - cancellationToken).ConfigureAwait(false); + cancellationToken); if (IsForEachProperty(symbol)) FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); @@ -156,6 +157,7 @@ await FindReferencesInDocumentUsingSymbolNameAsync( FindReferencesInDocumentInsideGlobalSuppressions( symbol, state, processResult, processResultData, cancellationToken); + return ValueTaskFactory.CompletedTask; } private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( From 293f1ba654bd4b47bfaaf6bd6ee6d64628d4f750 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:48:24 -0700 Subject: [PATCH 0938/1047] in progress --- .../Finders/NamespaceSymbolReferenceFinder.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index 229a035b6c08d..3ca848269ce6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -50,7 +51,7 @@ await FindDocumentsAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -60,16 +61,16 @@ protected override async ValueTask FindReferencesInDocumentAsync( { if (symbol.IsGlobalNamespace) { - await AddGlobalNamespaceReferencesAsync( - symbol, state, processResult, processResultData, 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, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -79,27 +80,29 @@ await AddNamedReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namespaceName, globalAlias)) continue; - await AddNamedReferencesAsync( - symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + AddNamedReferences( + symbol, globalAlias, 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); - await FindLocalAliasReferencesAsync( - initialReferences, symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindLocalAliasReferences( + initialReferences, symbol, state, processResult, processResultData, cancellationToken); FindReferencesInDocumentInsideGlobalSuppressions( symbol, state, processResult, processResultData, cancellationToken); } + + 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, @@ -109,11 +112,11 @@ private static async ValueTask AddNamedReferencesAsync( { var tokens = FindMatchingIdentifierTokens(state, name, cancellationToken); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static async Task AddGlobalNamespaceReferencesAsync( + private static void AddGlobalNamespaceReferences( INamespaceSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -126,7 +129,7 @@ private static async Task AddGlobalNamespaceReferencesAsync( static (token, state) => state.SyntaxFacts.IsGlobalNamespaceKeyword(token), state); - await FindReferencesInTokensAsync( - symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInTokens( + symbol, state, tokens, processResult, processResultData, cancellationToken); } } From e8f85f931af19fef9f145ae729eb42e981f39532 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:49:48 -0700 Subject: [PATCH 0939/1047] in progress --- .../Finders/OrdinaryMethodReferenceFinder.cs | 21 +++++++++++-------- .../PropertyAccessorSymbolReferenceFinder.cs | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 1131b5489010d..bb1dbb697400a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -108,7 +109,7 @@ 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, @@ -116,22 +117,24 @@ protected sealed override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (IsForEachMethod(symbol)) - await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInForEachStatements(symbol, state, processResult, processResultData, cancellationToken); if (IsDeconstructMethod(symbol)) - await FindReferencesInDeconstructionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDeconstruction(symbol, state, processResult, processResultData, cancellationToken); if (IsGetAwaiterMethod(symbol)) - await FindReferencesInAwaitExpressionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInAwaitExpression(symbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + symbol, state, processResult, processResultData, cancellationToken); if (IsAddMethod(symbol)) - await FindReferencesInCollectionInitializerAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInCollectionInitializer(symbol, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 7c12a31b19e68..cf0a657769c89 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -68,8 +68,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentUsingSymbolName( + symbol, state, processResult, processResultData, cancellationToken); if (symbol.AssociatedSymbol is not IPropertySymbol property || !options.AssociatePropertyReferencesWithSpecificAccessor) From 40c0f658085bdd08df53f0231885a8c49a8282cb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:51:54 -0700 Subject: [PATCH 0940/1047] in progress --- .../ConstructorSymbolReferenceFinder.cs | 42 ++++++++++--------- .../Finders/OperatorSymbolReferenceFinder.cs | 1 + 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 64fa5b86ced25..9e0d20f947dfd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -90,7 +90,7 @@ private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxF => syntaxFacts.TryGetPredefinedType(token, out var actualType) && predefinedType == actualType; - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, Action processResult, @@ -100,8 +100,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( { // 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, processResult, processResultData, 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) @@ -112,8 +112,8 @@ await AddReferencesInDocumentWorkerAsync( if (state.SyntaxFacts.StringComparer.Equals(name, globalAlias)) continue; - await AddReferencesInDocumentWorkerAsync( - methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddReferencesInDocumentWorker( + methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken); } // Finally, look for constructor references to predefined types (like `new int()`), @@ -121,18 +121,20 @@ await AddReferencesInDocumentWorkerAsync( FindPredefinedTypeReferences( methodSymbol, state, processResult, processResultData, cancellationToken); - await FindReferencesInImplicitObjectCreationExpressionAsync( - methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInImplicitObjectCreationExpression( + methodSymbol, state, processResult, processResultData, cancellationToken); FindReferencesInDocumentInsideGlobalSuppressions( methodSymbol, state, processResult, processResultData, cancellationToken); + + 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, @@ -140,13 +142,13 @@ private static async Task AddReferencesInDocumentWorkerAsync( TData processResultData, CancellationToken cancellationToken) { - await FindOrdinaryReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - await FindAttributeReferencesAsync( - symbol, name, state, processResult, processResultData, 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, @@ -154,7 +156,7 @@ private static ValueTask FindOrdinaryReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync( + FindReferencesInDocumentUsingIdentifier( symbol, name, state, processResult, processResultData, cancellationToken); } @@ -178,7 +180,7 @@ private static void FindPredefinedTypeReferences( FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask FindAttributeReferencesAsync( + private static void FindAttributeReferences( IMethodSymbol symbol, string name, FindReferencesDocumentState state, @@ -186,12 +188,11 @@ private static ValueTask FindAttributeReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, processResult, processResultData, cancellationToken) - : ValueTaskFactory.CompletedTask; + 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, @@ -208,7 +209,8 @@ private Task FindReferencesInImplicitObjectCreationExpressionAsync( ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); + return; static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index 8605e1f4ef4b4..a6d5cb8b80c0c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; From bab6cf37910f42289e2901479a2db2b0fbb78024 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:54:09 -0700 Subject: [PATCH 0941/1047] in progress --- .../Finders/NamedTypeSymbolReferenceFinder.cs | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 35816840c7d6c..0b2e5e08bb88c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -105,7 +105,7 @@ private static bool IsPotentialReference( predefinedType == actualType; } - protected override async ValueTask FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, Action processResult, @@ -117,8 +117,8 @@ protected override async ValueTask FindReferencesInDocumentAsync( // First find all references to this type, either with it's actual name, or through potential // global alises to it. - await AddReferencesToTypeOrGlobalAliasToItAsync( - namedType, state, StandardCallbacks.AddToArrayBuilder, 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) @@ -127,25 +127,27 @@ await AddReferencesToTypeOrGlobalAliasToItAsync( // This named type may end up being locally aliased as well. If so, now find all the references // to the local alias. - await FindLocalAliasReferencesAsync( - initialReferences, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindLocalAliasReferences( + initialReferences, state, processResult, processResultData, cancellationToken); - await FindPredefinedTypeReferencesAsync( - namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindPredefinedTypeReferences( + namedType, state, processResult, processResultData, cancellationToken); - await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindReferencesInDocumentInsideGlobalSuppressions( + namedType, state, processResult, processResultData, cancellationToken); + + return ValueTaskFactory.CompletedTask; } - internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( + internal static void AddReferencesToTypeOrGlobalAliasToIt( INamedTypeSymbol namedType, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { - await AddNonAliasReferencesAsync( - namedType, namedType.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, namedType.Name, state, processResult, processResultData, cancellationToken); foreach (var globalAlias in state.GlobalAliases) { @@ -155,8 +157,8 @@ await AddNonAliasReferencesAsync( if (state.SyntaxFacts.StringComparer.Equals(namedType.Name, globalAlias)) continue; - await AddNonAliasReferencesAsync( - namedType, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + AddNonAliasReferences( + namedType, globalAlias, state, processResult, processResultData, cancellationToken); } } @@ -165,7 +167,7 @@ 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, @@ -173,14 +175,14 @@ private static async ValueTask AddNonAliasReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - await FindOrdinaryReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindOrdinaryReferences( + symbol, name, state, processResult, processResultData, cancellationToken); - await FindAttributeReferencesAsync( - symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + FindAttributeReferences( + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindOrdinaryReferencesAsync( + private static void FindOrdinaryReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, @@ -193,11 +195,11 @@ private static ValueTask FindOrdinaryReferencesAsync( // 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( + FindReferencesInDocumentUsingIdentifier( namedType, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask FindPredefinedTypeReferencesAsync( + private static void FindPredefinedTypeReferences( INamedTypeSymbol symbol, FindReferencesDocumentState state, Action processResult, @@ -206,7 +208,7 @@ private static ValueTask FindPredefinedTypeReferencesAsync( { var predefinedType = symbol.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return ValueTaskFactory.CompletedTask; + return; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -214,10 +216,10 @@ private static ValueTask FindPredefinedTypeReferencesAsync( static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); + FindReferencesInTokens(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask FindAttributeReferencesAsync( + private static void FindAttributeReferences( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, @@ -225,8 +227,7 @@ private static ValueTask FindAttributeReferencesAsync( TData processResultData, CancellationToken cancellationToken) { - return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix) - ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken) - : ValueTaskFactory.CompletedTask; + if (TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix)) + FindReferencesInDocumentUsingIdentifier(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken); } } From 7917020dea28f93c0bfc1766700b0b41ef75949f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 13:56:40 -0700 Subject: [PATCH 0942/1047] All sync --- .../FindSymbols/FindReferences/BaseTypeFinder.cs | 9 +++++---- .../Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs | 5 ++--- .../Portable/Shared/Extensions/ITypeSymbolExtensions.cs | 8 +++----- 3 files changed, 10 insertions(+), 12 deletions(-) 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/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index 0799a49bccd41..ad70664b74512 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -157,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 && @@ -359,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/Shared/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.cs index 6bef4e0d8324e..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. From 751af977371ad257af8fdefb8ffab0786d941640 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 14:00:08 -0700 Subject: [PATCH 0943/1047] Passing --- .../Portable/GoToBase/AbstractGoToBaseService.cs | 2 +- .../Core/Portable/GoToBase/FindBaseHelpers.cs | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) 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 []; } } From 753ae44707e679967ddfef6cc15a332def4f8222 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 14:07:58 -0700 Subject: [PATCH 0944/1047] spelling' git push --- .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 5f2a538226dc2..7799a015a7e90 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -79,7 +79,7 @@ public async Task FindReferencesAsync( var channel = Channel.CreateUnbounded(s_channelOptions); await Task.WhenAll( - FindAllReferemcesAndWriteToChannelAsync(), + FindAllReferencesAndWriteToChannelAsync(), ReadReferencesFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); async Task ReadReferencesFromChannelAndReportToCallbackAsync() @@ -97,7 +97,7 @@ async Task ReadReferencesFromChannelAndReportToCallbackAsync() } } - async Task FindAllReferemcesAndWriteToChannelAsync() + async Task FindAllReferencesAndWriteToChannelAsync() { Exception? exception = null; try From d1689c3460a3ef111e43c4244c16fd3a554b3218 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 14:15:31 -0700 Subject: [PATCH 0945/1047] Delete --- .../SolutionCompilationState.SymbolToProjectId.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index 4340fe789c135..f94c87cbdea57 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -173,17 +173,6 @@ internal partial class SolutionCompilationState return projectId; } - else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && - typeParameter.TypeParameterKind == TypeParameterKind.Cref) - { - Debug.Fail(""); - return null; - - //// 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); - } return null; From 91465a7e5c9bcce539a0fa1f30668de2a1dc0984 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 14:27:00 -0700 Subject: [PATCH 0946/1047] Strong refs --- ...pilationTracker.CompilationTrackerState.cs | 15 ++-- ...tionCompilationState.CompilationTracker.cs | 6 +- ...eneratedFileReplacingCompilationTracker.cs | 2 +- ...utionCompilationState.UnrootedSymbolSet.cs | 74 ++++++------------- 4 files changed, 32 insertions(+), 65 deletions(-) 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 abffc060a6830..2195ec76a4226 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -147,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; + public readonly RootedSymbolSet RootedSymbolSet; /// /// The final compilation, with all references and source generators run. This is distinct from ? metadataReferenceToProjectId) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index 07548d764dde5..810ddcf13bf03 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -105,12 +105,10 @@ public bool ContainsAssemblyOrModuleOrDynamic( [NotNullWhen(true)] out Compilation? compilation, out MetadataReferenceInfo? referencedThrough) { - Debug.Assert(symbol.Kind is SymbolKind.Assembly or - SymbolKind.NetModule or - SymbolKind.DynamicType); + Debug.Assert(symbol.Kind is SymbolKind.Assembly or SymbolKind.NetModule or SymbolKind.DynamicType); var state = this.ReadState(); - var unrootedSymbolSet = (state as FinalCompilationTrackerState)?.UnrootedSymbolSet; + var unrootedSymbolSet = (state as FinalCompilationTrackerState)?.RootedSymbolSet; if (unrootedSymbolSet == null) { // this was not a tracker that has handed out a compilation (all compilations handed out must be diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 65d261a1f7960..d59b89fe1bcc9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -63,7 +63,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( return false; } - return UnrootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic( + return RootedSymbolSet.Create(_compilationWithReplacements).ContainsAssemblyOrModuleOrDynamic( symbol, primary, out compilation, out referencedThrough); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs index ae4707393ecb9..b027f07293312 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs @@ -2,17 +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 System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis; -using SecondaryReferencedSymbol = (int hashCode, WeakReference symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); +using SecondaryReferencedSymbol = (int hashCode, ISymbol symbol, SolutionCompilationState.MetadataReferenceInfo referenceInfo); internal partial class SolutionCompilationState { @@ -41,39 +38,20 @@ internal sealed record class OriginatingProjectInfo( MetadataReferenceInfo? ReferencedThrough); /// - /// A helper type for mapping back to an originating . + /// 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 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 + private readonly struct RootedSymbolSet { public readonly Compilation Compilation; - /// - /// 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 public readonly ImmutableArray SecondaryReferencedSymbols; - private UnrootedSymbolSet( + private RootedSymbolSet( Compilation compilation, - WeakReference primaryAssemblySymbol, - WeakReference primaryDynamicSymbol, ImmutableArray secondaryReferencedSymbols) { Compilation = compilation; - PrimaryAssemblySymbol = primaryAssemblySymbol; - PrimaryDynamicSymbol = primaryDynamicSymbol; SecondaryReferencedSymbols = secondaryReferencedSymbols; } - public static UnrootedSymbolSet Create(Compilation compilation) + public static RootedSymbolSet 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); @@ -114,14 +80,14 @@ public static UnrootedSymbolSet Create(Compilation compilation) if (symbol == null) continue; - secondarySymbols.Add((ReferenceEqualityComparer.GetHashCode(symbol), new WeakReference(symbol), MetadataReferenceInfo.From(reference))); + 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. + // 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(compilation, primaryAssembly, primaryDynamic, secondarySymbols.ToImmutable()); + return new RootedSymbolSet(compilation, secondarySymbols.ToImmutable()); } public bool ContainsAssemblyOrModuleOrDynamic( @@ -133,8 +99,14 @@ public bool ContainsAssemblyOrModuleOrDynamic( if (primary) { - if (symbol.Equals(this.PrimaryAssemblySymbol.GetTarget()) || - symbol.Equals(this.PrimaryDynamicSymbol.GetTarget())) + if (this.Compilation.Assembly.Equals(symbol)) + { + compilation = this.Compilation; + return true; + } + + if (this.Compilation.Language == LanguageNames.CSharp && + this.Compilation.DynamicType.Equals(symbol)) { compilation = this.Compilation; return true; @@ -163,7 +135,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) { var cached = secondarySymbols[index]; - if (cached.symbol.TryGetTarget(out var otherSymbol) && otherSymbol == symbol) + if (cached.symbol == symbol) { referencedThrough = cached.referenceInfo; compilation = this.Compilation; From 98f6cd77177b6467228b872a8aece381b317a292 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 14:30:31 -0700 Subject: [PATCH 0947/1047] Simplify --- ...ompilationTracker.CompilationTrackerState.cs | 17 ++++++++--------- ...lutionCompilationState.CompilationTracker.cs | 7 ++----- 2 files changed, 10 insertions(+), 14 deletions(-) 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 2195ec76a4226..347957df7875f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -149,7 +149,7 @@ private sealed class FinalCompilationTrackerState : CompilationTrackerState /// /// Used to determine which project an assembly symbol came from after the fact. /// - public readonly RootedSymbolSet RootedSymbolSet; + private SingleInitNullable _rootedSymbolSet; /// /// The final compilation, with all references and source generators run. This is distinct from _rootedSymbolSet.Initialize( + static finalCompilationWithGeneratedDocuments => RootedSymbolSet.Create(finalCompilationWithGeneratedDocuments), + this.FinalCompilationWithGeneratedDocuments); + public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPolicy) => creationPolicy == this.CreationPolicy ? this @@ -232,8 +232,7 @@ public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPo FinalCompilationWithGeneratedDocuments, CompilationWithoutGeneratedDocuments, HasSuccessfullyLoaded, - GeneratorInfo, - RootedSymbolSet); + 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 810ddcf13bf03..5f1b44eaf6799 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -106,10 +106,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( 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)?.RootedSymbolSet; - if (unrootedSymbolSet == null) + 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. @@ -118,7 +115,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( return false; } - return unrootedSymbolSet.Value.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out compilation, out referencedThrough); + return finalState.RootedSymbolSet.ContainsAssemblyOrModuleOrDynamic(symbol, primary, out compilation, out referencedThrough); } /// From 8a43b5cd232cd3f151d1a466ea557952ffd4c253 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 14:34:23 -0700 Subject: [PATCH 0948/1047] Use same helper --- .../AbstractNavigateToSearchService.CachedDocumentSearch.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 95513ef74c32a..9156f8c86e093 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -136,10 +136,8 @@ async ValueTask ProcessSingleProjectGroupAsync( var project = group.Key; // Break the project into high-pri docs and low pri docs, and process in that order. - var highPriDocs = group.Where(priorityDocumentKeysSet.Contains); - var lowPriDocs = group.Where(d => !highPriDocs.Contains(d)); await ParallelForEachAsync( - highPriDocs.Concat(lowPriDocs), + Prioritize(group, priorityDocumentKeysSet.Contains), cancellationToken, async (documentKey, cancellationToken) => { From 298b1ecd8c4a67ed1b4ccc0b04fed8d68a5422fb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 15:25:51 -0700 Subject: [PATCH 0949/1047] share code --- ...ateToSearchService.CachedDocumentSearch.cs | 4 +-- ...ToSearchService.GeneratedDocumentSearch.cs | 3 +- ...actNavigateToSearchService.NormalSearch.cs | 3 +- .../AbstractNavigateToSearchService.cs | 30 ++----------------- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 9156f8c86e093..faba6677a2c24 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; @@ -136,7 +136,7 @@ async ValueTask ProcessSingleProjectGroupAsync( var project = group.Key; // Break the project into high-pri docs and low pri docs, and process in that order. - await ParallelForEachAsync( + await RoslynParallel.ForEachAsync( Prioritize(group, priorityDocumentKeysSet.Contains), cancellationToken, async (documentKey, cancellationToken) => diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index e17ff05e12ab7..cc28b997adc84 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; @@ -75,7 +76,7 @@ async ValueTask ProcessSingleProjectAsync( // 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); - await ParallelForEachAsync( + await RoslynParallel.ForEachAsync( sourceGeneratedDocs, cancellationToken, (document, cancellationToken) => SearchSingleDocumentAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 0a6cd7a549ce5..e76b1cb7aa5cc 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -10,6 +10,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; @@ -131,7 +132,7 @@ async ValueTask SearchSingleProjectAsync( { using var _ = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); - await ParallelForEachAsync( + await RoslynParallel.ForEachAsync( Prioritize(project.Documents, highPriDocs.Contains), cancellationToken, (document, cancellationToken) => SearchSingleDocumentAsync( diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 3fc2865d93ed1..3cb8f1ad23fe0 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -9,6 +9,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.NavigateTo; @@ -140,34 +141,7 @@ async Task FindAllItemsAndWriteToChannelAsync() } Task PerformSearchAsync(Action onItemFound) - => ParallelForEachAsync( + => RoslynParallel.ForEachAsync( items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound, cancellationToken)); } - -#pragma warning disable CA1068 // CancellationToken parameters must come last - private static async Task ParallelForEachAsync( -#pragma warning restore CA1068 // CancellationToken parameters must come last - IEnumerable source, - CancellationToken cancellationToken, - Func body) - { - if (cancellationToken.IsCancellationRequested) - return; - -#if NET - await Parallel.ForEachAsync(source, cancellationToken, body).ConfigureAwait(false); -#else - using var _ = ArrayBuilder.GetInstance(out var tasks); - - foreach (var item in source) - { - tasks.Add(Task.Run(async () => - { - await body(item, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); -#endif - } } From 03b0fe7f48a4e89548985962705d8485132bd6fd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 15:26:33 -0700 Subject: [PATCH 0950/1047] move outside loop --- .../Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index 0c05d18f61007..f0ff5255f831b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -73,10 +73,10 @@ private sealed class LSPNavigateToCallback( public async Task AddResultsAsync(ImmutableArray results, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); + var solution = context.Solution; foreach (var result in results) { - var solution = context.Solution; var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); var location = await ProtocolConversions.TextSpanToLocationAsync( From 5bcb1ca94e21848102be8897b2c0982f4fde2c52 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 15:57:32 -0700 Subject: [PATCH 0951/1047] revert --- .../Core/Portable/FindSymbols/IFindReferencesProgress.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs index f4e7520000deb..af48d3cf3a972 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs @@ -13,6 +13,9 @@ public interface IFindReferencesProgress void OnStarted(); void OnCompleted(); + void OnFindInDocumentStarted(Document document); + void OnFindInDocumentCompleted(Document document); + void OnDefinitionFound(ISymbol symbol); void OnReferenceFound(ISymbol symbol, ReferenceLocation location); From 3bcce895749b8393dff23da63e9c293bcfa72f96 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 15:58:43 -0700 Subject: [PATCH 0952/1047] revert --- .../Portable/CodeLens/CodeLensFindReferenceProgress.cs | 8 ++++++++ .../FindSymbols/FindReferences/FindReferencesProgress.cs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs index 90336b1e720ec..5a0a535ac915c 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs @@ -58,6 +58,14 @@ public void OnCompleted() { } + public void OnFindInDocumentStarted(Document document) + { + } + + public void OnFindInDocumentCompleted(Document document) + { + } + private static bool FilterDefinition(ISymbol definition) { return definition.IsImplicitlyDeclared || diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs index f781b6f4fb0ee..747dc1dde863f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs @@ -35,4 +35,12 @@ public void OnDefinitionFound(ISymbol symbol) public void OnReferenceFound(ISymbol symbol, ReferenceLocation location) { } + + public void OnFindInDocumentStarted(Document document) + { + } + + public void OnFindInDocumentCompleted(Document document) + { + } } From 54639c63f540677f1a6d69b6c562a76f19e75043 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 16:05:05 -0700 Subject: [PATCH 0953/1047] pull notifications out --- .../FindReferencesSearchEngine.cs | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 7799a015a7e90..f085b2e979104 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -78,9 +78,19 @@ public async Task FindReferencesAsync( { var channel = Channel.CreateUnbounded(s_channelOptions); - await Task.WhenAll( - FindAllReferencesAndWriteToChannelAsync(), - ReadReferencesFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); + try + { + await Task.WhenAll( + FindAllReferencesAndWriteToChannelAsync(), + ReadReferencesFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + } + finally + { + await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); + } + + return; async Task ReadReferencesFromChannelAndReportToCallbackAsync() { @@ -122,43 +132,35 @@ async ValueTask PerformSearchAsync(Action onReferenceFound) 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); + var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); + await using var _ = disposable.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); + // 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); - // Report the initial set of symbols to the caller. - var allSymbols = symbolSet.GetAllSymbols(); - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + // Report the initial set of symbols to the caller. + var allSymbols = symbolSet.GetAllSymbols(); + await ReportGroupsAsync(allSymbols, cancellationToken).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); - - // 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. - await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); - - // 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), - cancellationToken, - async (tuple, cancellationToken) => await ProcessProjectAsync( - tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); - } - finally - { - await _progress.OnCompletedAsync(cancellationToken).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); + + // 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. + await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); + + // 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), + cancellationToken, + async (tuple, cancellationToken) => await ProcessProjectAsync( + tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); } async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( From ad08dd5aef857bb7ed8b5f5b20744a56e31115de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 16:29:47 -0700 Subject: [PATCH 0954/1047] Extract helper --- ...ateToSearchService.CachedDocumentSearch.cs | 6 +- ...ToSearchService.GeneratedDocumentSearch.cs | 6 +- ...actNavigateToSearchService.NormalSearch.cs | 14 ++-- .../AbstractNavigateToSearchService.cs | 57 ++++------------- .../IRemoteNavigateToSearchService.cs | 7 +- .../FindReferencesSearchEngine.cs | 45 ++----------- .../Shared/Extensions/ChannelExtensions.cs | 64 +++++++++++++++++++ 7 files changed, 100 insertions(+), 99 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index faba6677a2c24..f38292ca38901 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -73,7 +73,7 @@ public async Task SearchCachedDocumentsAsync( Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); var documentKeys = projects.SelectManyAsArray(p => p.Documents.Select(DocumentKey.ToDocumentKey)); var priorityDocumentKeys = priorityDocuments.SelectAsArray(DocumentKey.ToDocumentKey); @@ -81,7 +81,7 @@ public async Task SearchCachedDocumentsAsync( var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); await client.TryInvokeAsync( (service, callbackId, cancellationToken) => service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, [.. kinds], callbackId, cancellationToken), @@ -101,7 +101,7 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( ImmutableArray priorityDocumentKeys, string searchPattern, IImmutableSet kinds, - Func, Task> onItemsFound, + Func, CancellationToken, ValueTask> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index cc28b997adc84..937804b63f4a1 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -32,12 +32,12 @@ public async Task SearchGeneratedDocumentsAsync( Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); await client.TryInvokeAsync( // Sync and search the full solution snapshot. While this function is called serially per project, @@ -60,7 +60,7 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( ImmutableArray projects, string pattern, IImmutableSet kinds, - Func, Task> onItemsFound, + Func, CancellationToken, ValueTask> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index e76b1cb7aa5cc..44cdcdd304120 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -25,12 +25,12 @@ public async Task SearchDocumentAsync( CancellationToken cancellationToken) { var solution = document.Project.Solution; - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument: null, onResultsFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument: null, onResultsFound); var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted: null); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted: null, cancellationToken); // 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, @@ -45,7 +45,7 @@ await client.TryInvokeAsync( } public static async Task SearchDocumentInCurrentProcessAsync( - Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) + Document document, string searchPattern, IImmutableSet kinds, Func, CancellationToken, ValueTask> onItemsFound, CancellationToken cancellationToken) { var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); @@ -55,7 +55,7 @@ await SearchSingleDocumentAsync( document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); if (results.Count > 0) - await onItemsFound(results.ToImmutableArray()).ConfigureAwait(false); + await onItemsFound(results.ToImmutableArray(), cancellationToken).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -76,13 +76,13 @@ public async Task SearchProjectsAsync( Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { var priorityDocumentIds = priorityDocuments.SelectAsArray(d => d.Id); - var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); await client.TryInvokeAsync( // Intentionally sync the full solution. When SearchProjectAsync is called, we're searching all @@ -105,7 +105,7 @@ public static async Task SearchProjectsInCurrentProcessAsync( ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, - Func, Task> onItemsFound, + Func, CancellationToken, ValueTask> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 3cb8f1ad23fe0..8beacaee3dee0 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -9,6 +9,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -36,10 +37,10 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig public bool CanFilter => true; - private static Func, Task> GetOnItemsFoundCallback( - Solution solution, Document? activeDocument, Func, Task> onResultsFound, CancellationToken cancellationToken) + private static Func, CancellationToken, ValueTask> GetOnItemsFoundCallback( + Solution solution, Document? activeDocument, Func, Task> onResultsFound) { - return async items => + return async (items, cancellationToken) => { using var _ = ArrayBuilder.GetInstance(items.Length, out var results); @@ -94,54 +95,22 @@ private static IEnumerable Prioritize(IEnumerable items, Func private static async Task PerformParallelSearchAsync( IEnumerable items, Func, CancellationToken, ValueTask> callback, - Func, Task> onItemsFound, + Func, CancellationToken, ValueTask> onItemsFound, CancellationToken cancellationToken) { // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. // Concurrently, the reading task will grab items when available, and send them over to the host. var channel = Channel.CreateUnbounded(s_channelOptions); - await Task.WhenAll( - FindAllItemsAndWriteToChannelAsync(), - ReadItemsFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + await channel.BatchProcessAsync( + PerformSearchAsync, + onItemsFound, + cancellationToken).ConfigureAwait(false); - async Task ReadItemsFromChannelAndReportToCallbackAsync() - { - await Task.Yield().ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var items); - - while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - // Grab as many items as we can from the channel at once and report in a batch. - while (channel.Reader.TryRead(out var item)) - items.Add(item); - - await onItemsFound(items.ToImmutableAndClear()).ConfigureAwait(false); - } - } - - async Task FindAllItemsAndWriteToChannelAsync() - { - Exception? exception = null; - try - { - await Task.Yield().ConfigureAwait(false); - await PerformSearchAsync(item => channel.Writer.TryWrite(item)).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); - } - } + return; - Task PerformSearchAsync(Action onItemFound) - => RoslynParallel.ForEachAsync( - items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound, cancellationToken)); + async ValueTask PerformSearchAsync(Action onItemFound, CancellationToken cancellationToken) + => await RoslynParallel.ForEachAsync( + items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index 059b03a171a78..17d4af7c08d0b 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -47,14 +47,15 @@ public ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId) } internal sealed class NavigateToSearchServiceCallback( - Func, Task> onItemsFound, - Func? onProjectCompleted) + Func, CancellationToken, ValueTask> onItemsFound, + Func? onProjectCompleted, + CancellationToken cancellationToken) { public async ValueTask OnItemsFoundAsync(ImmutableArray items) { try { - await onItemsFound(items).ConfigureAwait(false); + await onItemsFound(items, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex)) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index f085b2e979104..92bcb56302ffa 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -81,9 +81,10 @@ public async Task FindReferencesAsync( await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - await Task.WhenAll( - FindAllReferencesAndWriteToChannelAsync(), - ReadReferencesFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + await channel.BatchProcessAsync( + PerformSearchAsync, + _progress.OnReferencesFoundAsync, + cancellationToken).ConfigureAwait(false); } finally { @@ -92,42 +93,8 @@ await Task.WhenAll( return; - async Task ReadReferencesFromChannelAndReportToCallbackAsync() - { - await Task.Yield().ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var references); - - while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - // Grab as many items as we can from the channel at once and report in a batch. - while (channel.Reader.TryRead(out var reference)) - references.Add(reference); - - await _progress.OnReferencesFoundAsync(references.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); - } - } - - async Task FindAllReferencesAndWriteToChannelAsync() - { - Exception? exception = null; - try - { - await Task.Yield().ConfigureAwait(false); - await PerformSearchAsync(item => channel.Writer.TryWrite(item)).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); - } - } - - async ValueTask PerformSearchAsync(Action onReferenceFound) + async ValueTask PerformSearchAsync( + Action onReferenceFound, CancellationToken cancellationToken) { var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); unifiedSymbols.AddRange(symbols); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs new file mode 100644 index 0000000000000..4ff6d7c4570d7 --- /dev/null +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Shared.Extensions; + +internal static class ChannelExtensions +{ + public static async Task BatchProcessAsync( + this Channel channel, + Func, CancellationToken, ValueTask> produceElementsAsync, + Func, CancellationToken, ValueTask> processBatchAsync, + CancellationToken cancellationToken) + { + await Task.WhenAll( + ProduceElementsAndWriteToChannelAsync(), + ReadElementsFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); + + return; + + async Task ReadElementsFromChannelAndReportToCallbackAsync() + { + await Task.Yield().ConfigureAwait(false); + using var _ = ArrayBuilder.GetInstance(out var batch); + + while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + // Grab as many items as we can from the channel at once and report in a batch. + while (channel.Reader.TryRead(out var item)) + batch.Add(item); + + await processBatchAsync(batch.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + } + } + + async Task ProduceElementsAndWriteToChannelAsync() + { + Exception? exception = null; + try + { + await Task.Yield().ConfigureAwait(false); + await produceElementsAsync(item => channel.Writer.TryWrite(item), cancellationToken).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); + } + } + } +} From ee3770e9c3602bd59b9e3c837896146d72715266 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 16:37:01 -0700 Subject: [PATCH 0955/1047] Usehelper --- .../Remote/Core/RemoteHostAssetWriter.cs | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index d2d31c20e7efd..7d65facbadf25 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; +using System.Collections.Immutable; using System.IO.Pipelines; using System.Threading; using System.Threading.Channels; @@ -95,43 +96,41 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) #else () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); #endif + // Keep track of how many checksums we found. We must find all the checksums we were asked to find. + var foundChecksumCount = 0; + var @this = this; - // Spin up a task to go find all the requested checksums, adding results to the channel. - // Spin up a task to read from the channel, writing out the assets to the pipe-writer. - await Task.WhenAll( - FindAssetsFromScopeAndWriteToChannelAsync(channel.Writer, cancellationToken), - ReadAssetsFromChannelAndWriteToPipeAsync(channel.Reader, cancellationToken)).ConfigureAwait(false); - } + await channel.BatchProcessAsync( + FindAssetsAsync, + WriteToPipeAsync, + cancellationToken).ConfigureAwait(false); - private async Task FindAssetsFromScopeAndWriteToChannelAsync(ChannelWriter channelWriter, CancellationToken cancellationToken) - { - Exception? exception = null; - try - { - await Task.Yield(); - - await _scope.FindAssetsAsync( - _assetPath, _checksums, - // 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 FindAssetsAsync completes or throws an exception) or if - // the cancellationToken is triggered above in WriteDataAsync. 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. - static (checksum, asset, channelWriter) => channelWriter.TryWrite((checksum, asset)), - channelWriter, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when ((exception = ex) == null) - { - throw ExceptionUtilities.Unreachable(); - } - finally + // If we weren't canceled, we better have found and written out all the expected assets. + Contract.ThrowIfTrue(foundChecksumCount != _checksums.Length); + + return; + + async ValueTask WriteToPipeAsync(ImmutableArray checksumsAndAssets, CancellationToken cancellationToken) { - // No matter what path we take (exceptional or non-exceptional), always complete the channel so the - // writing task knows it's done. - channelWriter.TryComplete(exception); + foundChecksumCount += checksumsAndAssets.Length; + await @this.WriteBatchToPipeAsync(checksumsAndAssets, cancellationToken).ConfigureAwait(false); } } - private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader channelReader, CancellationToken cancellationToken) + private async ValueTask FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) + { + await _scope.FindAssetsAsync( + _assetPath, _checksums, + // 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 FindAssetsAsync completes or throws an exception) or if + // the cancellationToken is triggered above in WriteDataAsync. 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. + static (checksum, asset, onItemFound) => onItemFound((checksum, asset)), + onItemFound, cancellationToken).ConfigureAwait(false); + } + + private async ValueTask WriteBatchToPipeAsync( + ImmutableArray checksumsAndAssets, CancellationToken cancellationToken) { await Task.Yield(); @@ -148,20 +147,14 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader Date: Sat, 27 Apr 2024 20:02:53 -0700 Subject: [PATCH 0956/1047] fix --- .../NavigateToSearch/RemoteNavigateToSearchService.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index e75d2e71600c4..4f8fb8c093453 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs @@ -30,12 +30,13 @@ public RemoteNavigateToSearchService(in ServiceConstructionArguments arguments, _callback = callback; } - private (Func, Task> onItemsFound, Func onProjectCompleted) GetCallbacks( + private (Func, CancellationToken, ValueTask> onItemsFound, Func onProjectCompleted) GetCallbacks( RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { - Func, Task> onItemsFound = async i => await _callback.InvokeAsync((callback, _) => - callback.OnItemsFoundAsync(callbackId, i), - cancellationToken).ConfigureAwait(false); + Func, CancellationToken, ValueTask> onItemsFound = + async (i, cancellationToken) => await _callback.InvokeAsync((callback, cancellationToken) => + callback.OnItemsFoundAsync(callbackId, i), + cancellationToken).ConfigureAwait(false); Func onProjectCompleted = async () => await _callback.InvokeAsync((callback, _) => callback.OnProjectCompletedAsync(callbackId), From 2f649575b72041137da652abe4c81b51110c67b4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:23:08 -0700 Subject: [PATCH 0957/1047] Fix --- ...utionCompilationState.UnrootedSymbolSet.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs index b027f07293312..b4e411122ed9c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs @@ -95,13 +95,12 @@ public bool ContainsAssemblyOrModuleOrDynamic( [NotNullWhen(true)] out Compilation? compilation, out MetadataReferenceInfo? referencedThrough) { - referencedThrough = null; - if (primary) { if (this.Compilation.Assembly.Equals(symbol)) { compilation = this.Compilation; + referencedThrough = null; return true; } @@ -109,43 +108,44 @@ public bool ContainsAssemblyOrModuleOrDynamic( this.Compilation.DynamicType.Equals(symbol)) { compilation = this.Compilation; + referencedThrough = null; return true; } } - - 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) + else { - compilation = null; - return false; - } + var secondarySymbols = this.SecondaryReferencedSymbols; - // 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--; + var symbolHash = ReferenceEqualityComparer.GetHashCode(symbol); - // 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 == 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) { - referencedThrough = cached.referenceInfo; - compilation = this.Compilation; - return true; + // 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 == symbol) + { + referencedThrough = cached.referenceInfo; + compilation = this.Compilation; + return true; + } + + index++; + } } - - index++; } compilation = null; + referencedThrough = null; return false; } } From df96626e50bb3fe5567ce460d79718a051c307dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:25:51 -0700 Subject: [PATCH 0958/1047] Make normal methods --- .../FindReferences/FindReferenceCache.cs | 97 ++++++++++--------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 3239d6045d33a..ba3cdc5889a6e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -121,72 +121,73 @@ public ImmutableArray FindMatchingIdentifierTokens( // // 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, _ => FindMatchingIdentifierTokensFromTree()) - : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText()); + ? _identifierCache.GetOrAdd(identifier, identifier => FindMatchingIdentifierTokensFromTree(identifier, cancellationToken)) + : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText(identifier, cancellationToken)); + } - bool IsMatch(string identifier, SyntaxToken token) - => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); + private bool IsMatch(string identifier, SyntaxToken token) + => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); - ImmutableArray FindMatchingIdentifierTokensFromTree() - { - using var _ = ArrayBuilder.GetInstance(out var result); - using var obj = SharedPools.Default>().GetPooledObject(); + 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); + var stack = obj.Object; + stack.Push(this.Root); - while (stack.TryPop(out var current)) + while (stack.TryPop(out var current)) + { + cancellationToken.ThrowIfCancellationRequested(); + if (current.IsNode) { - cancellationToken.ThrowIfCancellationRequested(); - if (current.IsNode) - { - foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) - stack.Push(child); - } - else if (current.IsToken) - { - var token = current.AsToken(); - if (IsMatch(identifier, token)) - result.Add(token); + foreach (var child in current.AsNode()!.ChildNodesAndTokens().Reverse()) + stack.Push(child); + } + else if (current.IsToken) + { + var token = current.AsToken(); + if (IsMatch(identifier, token)) + result.Add(token); - if (token.HasStructuredTrivia) + 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(); } - ImmutableArray FindMatchingIdentifierTokensFromText() - { - using var _ = ArrayBuilder.GetInstance(out var result); + return result.ToImmutableAndClear(); + } - var index = 0; - while ((index = this.Text.IndexOf(identifier, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) - { - cancellationToken.ThrowIfCancellationRequested(); + private ImmutableArray FindMatchingIdentifierTokensFromText( + string identifier, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var result); - 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); + 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.ToImmutableAndClear(); + 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) { From 13b1f1c5339d25da31735cd4dc3a88b0e1cb57dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:27:48 -0700 Subject: [PATCH 0959/1047] Spelling --- .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index f085b2e979104..c1c5deeb751e5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -329,7 +329,7 @@ private async Task ProcessDocumentAsync( // just grab those once here and hold onto them for the lifetime of this call. var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); - // This search almost always involves trying to find the tokens matching the nname of the symbol we'er looking + // 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. From 3ae4a7199ca5f29b4807756b7ed96b2fbd7d575c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:33:58 -0700 Subject: [PATCH 0960/1047] Simplify --- ...sSearchEngine_FindReferencesInDocuments.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 91852808ca728..b81e9bb931358 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -98,29 +98,36 @@ async ValueTask PerformSearchInDocumentAsync( } } - async ValueTask PerformSearchInDocumentWorkerAsync( - ISymbol symbol, FindReferencesDocumentState state) + async ValueTask PerformSearchInDocumentWorkerAsync(ISymbol symbol, FindReferencesDocumentState state) { - // Scratch buffer to place references for each finder. Cleared at the end of every loop iteration. - using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); - // Always perform a normal search, looking for direct references to exactly that symbol. + 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) { await finder.FindReferencesInDocumentAsync( symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in referencesForFinder) - { - var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); - await _progress.OnReferencesFoundAsync([(group, symbol, location)], cancellationToken).ConfigureAwait(false); - } - - referencesForFinder.Clear(); } - // 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 = AbstractReferenceFinder.FindMatchingIdentifierTokens(state, symbol.Name, cancellationToken); From 432b3fdb9d8514dde81c4eeee02393fabd8a2ee6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:35:44 -0700 Subject: [PATCH 0961/1047] remove --- .../FindReferences/Finders/AbstractReferenceFinder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index b370fdfd0e747..aeb052995d490 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -375,7 +375,6 @@ protected static void FindReferencesInDocument( TData processResultData, CancellationToken cancellationToken) { - var document = state.Document; var syntaxTreeInfo = state.Cache.SyntaxTreeIndex; if (isRelevantDocument(syntaxTreeInfo)) { From dc8b00d61d3babbd415d4a071e19b387d5f1716e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:36:28 -0700 Subject: [PATCH 0962/1047] Rename file --- ...edSymbolSet.cs => SolutionCompilationState.RootedSymbolSet.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Solution/{SolutionCompilationState.UnrootedSymbolSet.cs => SolutionCompilationState.RootedSymbolSet.cs} (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.UnrootedSymbolSet.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs From d30867d6f6885931a8775cbba12a8f80b5c4ed32 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:37:17 -0700 Subject: [PATCH 0963/1047] Equals --- .../Solution/SolutionCompilationState.RootedSymbolSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs index b4e411122ed9c..871058f9fac64 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -132,7 +132,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) { var cached = secondarySymbols[index]; - if (cached.symbol == symbol) + if (Equals(cached.symbol, symbol)) { referencedThrough = cached.referenceInfo; compilation = this.Compilation; From 0a32ab95f47ae614783f48035025030acd7c494e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 20:39:28 -0700 Subject: [PATCH 0964/1047] Equals --- .../Solution/SolutionCompilationState.RootedSymbolSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs index 871058f9fac64..09b331376a792 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -132,7 +132,7 @@ public bool ContainsAssemblyOrModuleOrDynamic( while (index < secondarySymbols.Length && secondarySymbols[index].hashCode == symbolHash) { var cached = secondarySymbols[index]; - if (Equals(cached.symbol, symbol)) + if (cached.symbol.Equals(symbol)) { referencedThrough = cached.referenceInfo; compilation = this.Compilation; From 9193069d27fb3711961cf73988dffd6ce000984b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 23:18:30 -0700 Subject: [PATCH 0965/1047] restore --- .../Solution/SolutionCompilationState.RootedSymbolSet.cs | 2 +- .../SolutionCompilationState.SymbolToProjectId.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs index 09b331376a792..c33523099f78e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -34,7 +34,7 @@ internal static MetadataReferenceInfo From(MetadataReference reference) /// internal sealed record class OriginatingProjectInfo( ProjectId ProjectId, - Compilation Compilation, + Compilation? Compilation, MetadataReferenceInfo? ReferencedThrough); /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index f94c87cbdea57..d0d6b04e5d889 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -173,6 +173,14 @@ internal partial class SolutionCompilationState return projectId; } + else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && + typeParameter.TypeParameterKind == TypeParameterKind.Cref) + { + // 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, Compilation: null, ReferencedThrough: null); + } return null; From 2dffbc287895748196232ee9eac16cf89b02da26 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 23:25:28 -0700 Subject: [PATCH 0966/1047] Work --- .../SolutionCompilationState.SymbolToProjectId.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index d0d6b04e5d889..5e86a295e2ef4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -173,12 +173,15 @@ 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) + if (GetDocumentState(typeParameterSourceTree, projectId: null) is { } document) return new OriginatingProjectInfo(document.Id.ProjectId, Compilation: null, ReferencedThrough: null); } From 48fdc8e83c310e29d8572dd0870ce52babdef1f2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 23:18:30 -0700 Subject: [PATCH 0967/1047] restore --- .../Solution/SolutionCompilationState.RootedSymbolSet.cs | 2 +- .../SolutionCompilationState.SymbolToProjectId.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs index 09b331376a792..c33523099f78e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.RootedSymbolSet.cs @@ -34,7 +34,7 @@ internal static MetadataReferenceInfo From(MetadataReference reference) /// internal sealed record class OriginatingProjectInfo( ProjectId ProjectId, - Compilation Compilation, + Compilation? Compilation, MetadataReferenceInfo? ReferencedThrough); /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index f94c87cbdea57..d0d6b04e5d889 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -173,6 +173,14 @@ internal partial class SolutionCompilationState return projectId; } + else if (symbol.IsKind(SymbolKind.TypeParameter, out ITypeParameterSymbol? typeParameter) && + typeParameter.TypeParameterKind == TypeParameterKind.Cref) + { + // 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, Compilation: null, ReferencedThrough: null); + } return null; From 176e23922c675a3dfb1cb2d720721e1d7838b663 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 27 Apr 2024 23:25:28 -0700 Subject: [PATCH 0968/1047] Work --- .../SolutionCompilationState.SymbolToProjectId.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs index d0d6b04e5d889..5e86a295e2ef4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SymbolToProjectId.cs @@ -173,12 +173,15 @@ 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) + if (GetDocumentState(typeParameterSourceTree, projectId: null) is { } document) return new OriginatingProjectInfo(document.Id.ProjectId, Compilation: null, ReferencedThrough: null); } From f82e8d59eeea6756d1dc335a0390b739269805ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 09:39:08 -0700 Subject: [PATCH 0969/1047] Share code --- .../AbstractNavigateToSearchService.cs | 2 +- .../FindReferencesSearchEngine.cs | 2 +- .../Shared/Extensions/ChannelExtensions.cs | 93 ++++++++++++++++--- .../Remote/Core/RemoteHostAssetWriter.cs | 40 +++----- 4 files changed, 94 insertions(+), 43 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 8beacaee3dee0..f9c91aa596443 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -102,7 +102,7 @@ private static async Task PerformParallelSearchAsync( // Concurrently, the reading task will grab items when available, and send them over to the host. var channel = Channel.CreateUnbounded(s_channelOptions); - await channel.BatchProcessAsync( + await channel.RunProducerConsumerAsync( PerformSearchAsync, onItemsFound, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 9eada2c7190ea..e65812611eeb4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -81,7 +81,7 @@ public async Task FindReferencesAsync( await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - await channel.BatchProcessAsync( + await channel.RunProducerConsumerAsync( PerformSearchAsync, _progress.OnReferencesFoundAsync, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index 4ff6d7c4570d7..efd67a620c285 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Channels; @@ -14,32 +15,100 @@ namespace Microsoft.CodeAnalysis.Shared.Extensions; internal static class ChannelExtensions { - public static async Task BatchProcessAsync( + /// + /// Version of when caller the prefers the results being pre-packaged into arrays + /// to process. + /// + public static Task RunProducerConsumerAsync( this Channel channel, Func, CancellationToken, ValueTask> produceElementsAsync, - Func, CancellationToken, ValueTask> processBatchAsync, + Func, CancellationToken, ValueTask> consumeElementsAsync, CancellationToken cancellationToken) { - await Task.WhenAll( - ProduceElementsAndWriteToChannelAsync(), - ReadElementsFromChannelAndReportToCallbackAsync()).ConfigureAwait(false); - - return; + return RunProducerConsumerImplAsync( + channel, + produceElementsAsync, + ConsumeElementsAsArrayAsync, + cancellationToken); - async Task ReadElementsFromChannelAndReportToCallbackAsync() + async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader, CancellationToken token) { - await Task.Yield().ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(out var batch); - while (await channel.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - // Grab as many items as we can from the channel at once and report in a batch. + // 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 (channel.Reader.TryRead(out var item)) batch.Add(item); - await processBatchAsync(batch.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + await consumeElementsAsync(batch.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); } } + } + + /// + /// Version of when the caller prefers working with a stream of results. + /// + public static Task RunProducerConsumerAsync( + this Channel channel, + Func, CancellationToken, ValueTask> produceElementsAsync, + Func, CancellationToken, ValueTask> consumeElementsAsync, + CancellationToken cancellationToken) + { + return RunProducerConsumerImplAsync( + channel, + produceElementsAsync, + ConsumeElementsAsStreamAsync, + cancellationToken); + + async ValueTask ConsumeElementsAsStreamAsync(ChannelReader reader, CancellationToken cancellationToken) + { + await consumeElementsAsync(reader.ReadAllAsync(cancellationToken), cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Helper utility for the pattern of a pair of a production routine and consumption routine using a channel to + /// coordinate data transfer. The channel itself is provided by the caller, but manages the rules and behaviors + /// around the routines. Importantly, it 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 elements. It will be passed an action that can be used to write elements 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 elements. + /// + private static async Task RunProducerConsumerImplAsync( + this Channel channel, + Func, CancellationToken, ValueTask> produceElementsAsync, + Func, CancellationToken, ValueTask> consumeElementsAsync, + CancellationToken cancellationToken) + { + // When cancellation happens, attempt to close the channel. That will unblock the task processing the elements. + // 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( + ProduceElementsAndWriteToChannelAsync(), + ReadFromChannelAndConsumeElementsAsync()).ConfigureAwait(false); + + return; + + async Task ReadFromChannelAndConsumeElementsAsync() + { + await Task.Yield().ConfigureAwait(false); + await consumeElementsAsync(channel.Reader, cancellationToken).ConfigureAwait(false); + } async Task ProduceElementsAndWriteToChannelAsync() { diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 7d65facbadf25..b95bed855ff41 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -4,7 +4,7 @@ using System; using System.Buffers.Binary; -using System.Collections.Immutable; +using System.Collections.Generic; using System.IO.Pipelines; using System.Threading; using System.Threading.Channels; @@ -87,34 +87,10 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. var channel = Channel.CreateUnbounded(s_channelOptions); - // When cancellation happens, attempt to close the channel. That will unblock the task writing the assets to - // the pipe. 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 - // Keep track of how many checksums we found. We must find all the checksums we were asked to find. - var foundChecksumCount = 0; - var @this = this; - - await channel.BatchProcessAsync( + await channel.RunProducerConsumerAsync( FindAssetsAsync, - WriteToPipeAsync, + WriteBatchToPipeAsync, cancellationToken).ConfigureAwait(false); - - // If we weren't canceled, we better have found and written out all the expected assets. - Contract.ThrowIfTrue(foundChecksumCount != _checksums.Length); - - return; - - async ValueTask WriteToPipeAsync(ImmutableArray checksumsAndAssets, CancellationToken cancellationToken) - { - foundChecksumCount += checksumsAndAssets.Length; - await @this.WriteBatchToPipeAsync(checksumsAndAssets, cancellationToken).ConfigureAwait(false); - } } private async ValueTask FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) @@ -130,7 +106,7 @@ await _scope.FindAssetsAsync( } private async ValueTask WriteBatchToPipeAsync( - ImmutableArray checksumsAndAssets, CancellationToken cancellationToken) + IAsyncEnumerable checksumsAndAssets, CancellationToken cancellationToken) { await Task.Yield(); @@ -147,14 +123,20 @@ private async ValueTask WriteBatchToPipeAsync( _scope.SolutionChecksum.WriteTo(_pipeWriter); await _pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false); - foreach (var (checksum, asset) in checksumsAndAssets) + // 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( From c20907a794d506049eca6294387509873665b0e8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 09:46:31 -0700 Subject: [PATCH 0970/1047] revert --- ...ateToSearchService.CachedDocumentSearch.cs | 9 +++---- ...ToSearchService.GeneratedDocumentSearch.cs | 8 +++--- ...actNavigateToSearchService.NormalSearch.cs | 17 ++++++------ .../AbstractNavigateToSearchService.cs | 14 +++++----- .../IRemoteNavigateToSearchService.cs | 7 +++-- .../FindReferencesSearchEngine.cs | 5 ++-- .../Shared/Extensions/ChannelExtensions.cs | 26 +++++++++---------- .../Remote/Core/RemoteHostAssetWriter.cs | 7 +++-- 8 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index f38292ca38901..9b571904c7265 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -73,7 +73,7 @@ public async Task SearchCachedDocumentsAsync( Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); var documentKeys = projects.SelectManyAsArray(p => p.Documents.Select(DocumentKey.ToDocumentKey)); var priorityDocumentKeys = priorityDocuments.SelectAsArray(DocumentKey.ToDocumentKey); @@ -81,7 +81,7 @@ public async Task SearchCachedDocumentsAsync( var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); await client.TryInvokeAsync( (service, callbackId, cancellationToken) => service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, [.. kinds], callbackId, cancellationToken), @@ -101,7 +101,7 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( ImmutableArray priorityDocumentKeys, string searchPattern, IImmutableSet kinds, - Func, CancellationToken, ValueTask> onItemsFound, + Func, ValueTask> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -127,8 +127,7 @@ await PerformParallelSearchAsync( async ValueTask ProcessSingleProjectGroupAsync( IGrouping group, - Action onItemFound, - CancellationToken cancellationToken) + Action onItemFound) { if (cancellationToken.IsCancellationRequested) return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 937804b63f4a1..4304a887e9b17 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -32,12 +32,12 @@ public async Task SearchGeneratedDocumentsAsync( Contract.ThrowIfTrue(projects.IsEmpty); Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); + var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound, cancellationToken); var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); if (client != null) { - var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted, cancellationToken); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); await client.TryInvokeAsync( // Sync and search the full solution snapshot. While this function is called serially per project, @@ -60,7 +60,7 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( ImmutableArray projects, string pattern, IImmutableSet kinds, - Func, CancellationToken, ValueTask> onItemsFound, + Func, ValueTask> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -71,7 +71,7 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( return; async ValueTask ProcessSingleProjectAsync( - Project project, Action onItemFound, CancellationToken cancellationToken) + Project project, Action onItemFound) { // 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); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 44cdcdd304120..26c362eb7a7c3 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -25,12 +25,12 @@ public async Task SearchDocumentAsync( CancellationToken cancellationToken) { var solution = document.Project.Solution; - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument: null, onResultsFound); + 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(onItemsFound, onProjectCompleted: null, cancellationToken); + 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, @@ -45,7 +45,7 @@ await client.TryInvokeAsync( } public static async Task SearchDocumentInCurrentProcessAsync( - Document document, string searchPattern, IImmutableSet kinds, Func, CancellationToken, ValueTask> onItemsFound, CancellationToken cancellationToken) + Document document, string searchPattern, IImmutableSet kinds, Func, ValueTask> onItemsFound, CancellationToken cancellationToken) { var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); @@ -55,7 +55,7 @@ await SearchSingleDocumentAsync( document, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, t => results.Add(t), cancellationToken).ConfigureAwait(false); if (results.Count > 0) - await onItemsFound(results.ToImmutableArray(), cancellationToken).ConfigureAwait(false); + await onItemsFound(results.ToImmutableArray()).ConfigureAwait(false); } public async Task SearchProjectsAsync( @@ -76,13 +76,13 @@ public async Task SearchProjectsAsync( Contract.ThrowIfTrue(projects.Select(p => p.Language).Distinct().Count() != 1); Debug.Assert(priorityDocuments.All(d => projects.Contains(d.Project))); - var onItemsFound = GetOnItemsFoundCallback(solution, activeDocument, onResultsFound); + 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(onItemsFound, onProjectCompleted, cancellationToken); + var callback = new NavigateToSearchServiceCallback(onItemsFound, onProjectCompleted); await client.TryInvokeAsync( // Intentionally sync the full solution. When SearchProjectAsync is called, we're searching all @@ -105,7 +105,7 @@ public static async Task SearchProjectsInCurrentProcessAsync( ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, - Func, CancellationToken, ValueTask> onItemsFound, + Func, ValueTask> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { @@ -127,8 +127,7 @@ await PerformParallelSearchAsync( async ValueTask SearchSingleProjectAsync( Project project, - Action onItemFound, - CancellationToken cancellationToken) + Action onItemFound) { using var _ = GetPooledHashSet(priorityDocuments.Where(d => project == d.Project), out var highPriDocs); diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index f9c91aa596443..b7d6e8d26cc32 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -37,10 +37,10 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig public bool CanFilter => true; - private static Func, CancellationToken, ValueTask> GetOnItemsFoundCallback( - Solution solution, Document? activeDocument, Func, Task> onResultsFound) + private static Func, ValueTask> GetOnItemsFoundCallback( + Solution solution, Document? activeDocument, Func, Task> onResultsFound, CancellationToken cancellationToken) { - return async (items, cancellationToken) => + return async items => { using var _ = ArrayBuilder.GetInstance(items.Length, out var results); @@ -94,8 +94,8 @@ private static IEnumerable Prioritize(IEnumerable items, Func /// private static async Task PerformParallelSearchAsync( IEnumerable items, - Func, CancellationToken, ValueTask> callback, - Func, CancellationToken, ValueTask> onItemsFound, + Func, ValueTask> callback, + Func, ValueTask> onItemsFound, CancellationToken cancellationToken) { // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. @@ -109,8 +109,8 @@ await channel.RunProducerConsumerAsync( return; - async ValueTask PerformSearchAsync(Action onItemFound, CancellationToken cancellationToken) + async ValueTask PerformSearchAsync(Action onItemFound) => await RoslynParallel.ForEachAsync( - items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound, cancellationToken)).ConfigureAwait(false); + items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)).ConfigureAwait(false); } } diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index 17d4af7c08d0b..8bceb82611086 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -47,15 +47,14 @@ public ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId) } internal sealed class NavigateToSearchServiceCallback( - Func, CancellationToken, ValueTask> onItemsFound, - Func? onProjectCompleted, - CancellationToken cancellationToken) + Func, ValueTask> onItemsFound, + Func? onProjectCompleted) { public async ValueTask OnItemsFoundAsync(ImmutableArray items) { try { - await onItemsFound(items, cancellationToken).ConfigureAwait(false); + await onItemsFound(items).ConfigureAwait(false); } catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex)) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index e65812611eeb4..4010e681b2300 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -83,7 +83,7 @@ public async Task FindReferencesAsync( { await channel.RunProducerConsumerAsync( PerformSearchAsync, - _progress.OnReferencesFoundAsync, + references => _progress.OnReferencesFoundAsync(references, cancellationToken), cancellationToken).ConfigureAwait(false); } finally @@ -93,8 +93,7 @@ await channel.RunProducerConsumerAsync( return; - async ValueTask PerformSearchAsync( - Action onReferenceFound, CancellationToken cancellationToken) + async ValueTask PerformSearchAsync(Action onReferenceFound) { var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); unifiedSymbols.AddRange(symbols); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index efd67a620c285..15c4b6fe838ee 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -21,8 +21,8 @@ internal static class ChannelExtensions /// public static Task RunProducerConsumerAsync( this Channel channel, - Func, CancellationToken, ValueTask> produceElementsAsync, - Func, CancellationToken, ValueTask> consumeElementsAsync, + Func, ValueTask> produceElementsAsync, + Func, ValueTask> consumeElementsAsync, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( @@ -31,7 +31,7 @@ public static Task RunProducerConsumerAsync( ConsumeElementsAsArrayAsync, cancellationToken); - async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader, CancellationToken token) + async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader) { using var _ = ArrayBuilder.GetInstance(out var batch); @@ -42,7 +42,7 @@ async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader, Canc while (channel.Reader.TryRead(out var item)) batch.Add(item); - await consumeElementsAsync(batch.ToImmutableAndClear(), cancellationToken).ConfigureAwait(false); + await consumeElementsAsync(batch.ToImmutableAndClear()).ConfigureAwait(false); } } } @@ -52,8 +52,8 @@ async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader, Canc /// public static Task RunProducerConsumerAsync( this Channel channel, - Func, CancellationToken, ValueTask> produceElementsAsync, - Func, CancellationToken, ValueTask> consumeElementsAsync, + Func, ValueTask> produceElementsAsync, + Func, ValueTask> consumeElementsAsync, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( @@ -62,10 +62,8 @@ public static Task RunProducerConsumerAsync( ConsumeElementsAsStreamAsync, cancellationToken); - async ValueTask ConsumeElementsAsStreamAsync(ChannelReader reader, CancellationToken cancellationToken) - { - await consumeElementsAsync(reader.ReadAllAsync(cancellationToken), cancellationToken).ConfigureAwait(false); - } + ValueTask ConsumeElementsAsStreamAsync(ChannelReader reader) + => consumeElementsAsync(reader.ReadAllAsync(cancellationToken)); } /// @@ -84,8 +82,8 @@ async ValueTask ConsumeElementsAsStreamAsync(ChannelReader reader, Can /// private static async Task RunProducerConsumerImplAsync( this Channel channel, - Func, CancellationToken, ValueTask> produceElementsAsync, - Func, CancellationToken, ValueTask> consumeElementsAsync, + Func, ValueTask> produceElementsAsync, + Func, ValueTask> consumeElementsAsync, CancellationToken cancellationToken) { // When cancellation happens, attempt to close the channel. That will unblock the task processing the elements. @@ -107,7 +105,7 @@ await Task.WhenAll( async Task ReadFromChannelAndConsumeElementsAsync() { await Task.Yield().ConfigureAwait(false); - await consumeElementsAsync(channel.Reader, cancellationToken).ConfigureAwait(false); + await consumeElementsAsync(channel.Reader).ConfigureAwait(false); } async Task ProduceElementsAndWriteToChannelAsync() @@ -116,7 +114,7 @@ async Task ProduceElementsAndWriteToChannelAsync() try { await Task.Yield().ConfigureAwait(false); - await produceElementsAsync(item => channel.Writer.TryWrite(item), cancellationToken).ConfigureAwait(false); + await produceElementsAsync(item => channel.Writer.TryWrite(item)).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index b95bed855ff41..5d3b0e7c04705 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -87,9 +87,10 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. var channel = Channel.CreateUnbounded(s_channelOptions); + var @this = this; await channel.RunProducerConsumerAsync( - FindAssetsAsync, - WriteBatchToPipeAsync, + onItemFound => @this.FindAssetsAsync(onItemFound, cancellationToken), + items => @this.WriteBatchToPipeAsync(items, cancellationToken), cancellationToken).ConfigureAwait(false); } @@ -108,8 +109,6 @@ await _scope.FindAssetsAsync( private async ValueTask WriteBatchToPipeAsync( IAsyncEnumerable checksumsAndAssets, CancellationToken cancellationToken) { - await Task.Yield(); - // 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 From 4e484874d50717742bcc32d1527b60d87fae66ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 09:47:46 -0700 Subject: [PATCH 0971/1047] revert --- .../NavigateToSearch/RemoteNavigateToSearchService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index 4f8fb8c093453..f2983fc890c92 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs @@ -30,11 +30,11 @@ public RemoteNavigateToSearchService(in ServiceConstructionArguments arguments, _callback = callback; } - private (Func, CancellationToken, ValueTask> onItemsFound, Func onProjectCompleted) GetCallbacks( + private (Func, ValueTask> onItemsFound, Func onProjectCompleted) GetCallbacks( RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { - Func, CancellationToken, ValueTask> onItemsFound = - async (i, cancellationToken) => await _callback.InvokeAsync((callback, cancellationToken) => + Func, ValueTask> onItemsFound = + async i => await _callback.InvokeAsync((callback, cancellationToken) => callback.OnItemsFoundAsync(callbackId, i), cancellationToken).ConfigureAwait(false); From ed110957f5d581e832b271d1d8396eaf38d639f3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:04:54 -0700 Subject: [PATCH 0972/1047] use tasks --- ...vigateToSearchService.CachedDocumentSearch.cs | 2 +- ...ateToSearchService.GeneratedDocumentSearch.cs | 2 +- ...stractNavigateToSearchService.NormalSearch.cs | 4 ++-- .../AbstractNavigateToSearchService.cs | 10 +++++----- .../NavigateTo/IRemoteNavigateToSearchService.cs | 2 +- .../FindReferences/FindReferencesSearchEngine.cs | 4 ++-- .../Shared/Extensions/ChannelExtensions.cs | 16 ++++++++-------- .../Remote/Core/RemoteHostAssetWriter.cs | 10 ++++------ .../RemoteNavigateToSearchService.cs | 11 +++++------ 9 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 9b571904c7265..015ea7d4a04f7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -101,7 +101,7 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync( ImmutableArray priorityDocumentKeys, string searchPattern, IImmutableSet kinds, - Func, ValueTask> onItemsFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 4304a887e9b17..370fa6fc8fb71 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -60,7 +60,7 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync( ImmutableArray projects, string pattern, IImmutableSet kinds, - Func, ValueTask> onItemsFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index 26c362eb7a7c3..33f95b3cf70a7 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -45,7 +45,7 @@ await client.TryInvokeAsync( } public static async Task SearchDocumentInCurrentProcessAsync( - Document document, string searchPattern, IImmutableSet kinds, Func, ValueTask> onItemsFound, CancellationToken cancellationToken) + Document document, string searchPattern, IImmutableSet kinds, Func, Task> onItemsFound, CancellationToken cancellationToken) { var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(searchPattern); var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds); @@ -105,7 +105,7 @@ public static async Task SearchProjectsInCurrentProcessAsync( ImmutableArray priorityDocuments, string searchPattern, IImmutableSet kinds, - Func, ValueTask> onItemsFound, + Func, Task> onItemsFound, Func onProjectCompleted, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index b7d6e8d26cc32..97d90bcdddd58 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -37,7 +37,7 @@ internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavig public bool CanFilter => true; - private static Func, ValueTask> GetOnItemsFoundCallback( + private static Func, Task> GetOnItemsFoundCallback( Solution solution, Document? activeDocument, Func, Task> onResultsFound, CancellationToken cancellationToken) { return async items => @@ -95,7 +95,7 @@ private static IEnumerable Prioritize(IEnumerable items, Func private static async Task PerformParallelSearchAsync( IEnumerable items, Func, ValueTask> callback, - Func, ValueTask> onItemsFound, + Func, Task> onItemsFound, CancellationToken cancellationToken) { // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. @@ -109,8 +109,8 @@ await channel.RunProducerConsumerAsync( return; - async ValueTask PerformSearchAsync(Action onItemFound) - => await RoslynParallel.ForEachAsync( - items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)).ConfigureAwait(false); + Task PerformSearchAsync(Action onItemFound) + => RoslynParallel.ForEachAsync( + items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)); } } diff --git a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs index 8bceb82611086..059b03a171a78 100644 --- a/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/IRemoteNavigateToSearchService.cs @@ -47,7 +47,7 @@ public ValueTask OnProjectCompletedAsync(RemoteServiceCallbackId callbackId) } internal sealed class NavigateToSearchServiceCallback( - Func, ValueTask> onItemsFound, + Func, Task> onItemsFound, Func? onProjectCompleted) { public async ValueTask OnItemsFoundAsync(ImmutableArray items) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 4010e681b2300..80f745db995ef 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -83,7 +83,7 @@ public async Task FindReferencesAsync( { await channel.RunProducerConsumerAsync( PerformSearchAsync, - references => _progress.OnReferencesFoundAsync(references, cancellationToken), + async references => await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } finally @@ -93,7 +93,7 @@ await channel.RunProducerConsumerAsync( return; - async ValueTask PerformSearchAsync(Action onReferenceFound) + async Task PerformSearchAsync(Action onReferenceFound) { var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); unifiedSymbols.AddRange(symbols); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index 15c4b6fe838ee..78be88081bb60 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -21,8 +21,8 @@ internal static class ChannelExtensions /// public static Task RunProducerConsumerAsync( this Channel channel, - Func, ValueTask> produceElementsAsync, - Func, ValueTask> consumeElementsAsync, + Func, Task> produceElementsAsync, + Func, Task> consumeElementsAsync, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( @@ -31,7 +31,7 @@ public static Task RunProducerConsumerAsync( ConsumeElementsAsArrayAsync, cancellationToken); - async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader) + async Task ConsumeElementsAsArrayAsync(ChannelReader reader) { using var _ = ArrayBuilder.GetInstance(out var batch); @@ -52,8 +52,8 @@ async ValueTask ConsumeElementsAsArrayAsync(ChannelReader reader) /// public static Task RunProducerConsumerAsync( this Channel channel, - Func, ValueTask> produceElementsAsync, - Func, ValueTask> consumeElementsAsync, + Func, Task> produceElementsAsync, + Func, Task> consumeElementsAsync, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( @@ -62,7 +62,7 @@ public static Task RunProducerConsumerAsync( ConsumeElementsAsStreamAsync, cancellationToken); - ValueTask ConsumeElementsAsStreamAsync(ChannelReader reader) + Task ConsumeElementsAsStreamAsync(ChannelReader reader) => consumeElementsAsync(reader.ReadAllAsync(cancellationToken)); } @@ -82,8 +82,8 @@ ValueTask ConsumeElementsAsStreamAsync(ChannelReader reader) /// private static async Task RunProducerConsumerImplAsync( this Channel channel, - Func, ValueTask> produceElementsAsync, - Func, ValueTask> consumeElementsAsync, + Func, Task> produceElementsAsync, + Func, Task> consumeElementsAsync, CancellationToken cancellationToken) { // When cancellation happens, attempt to close the channel. That will unblock the task processing the elements. diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 5d3b0e7c04705..b577a9291555d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -94,19 +94,17 @@ await channel.RunProducerConsumerAsync( cancellationToken).ConfigureAwait(false); } - private async ValueTask FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) - { - await _scope.FindAssetsAsync( + private Task FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) + => _scope.FindAssetsAsync( _assetPath, _checksums, // 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 FindAssetsAsync completes or throws an exception) or if // the cancellationToken is triggered above in WriteDataAsync. 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. static (checksum, asset, onItemFound) => onItemFound((checksum, asset)), - onItemFound, cancellationToken).ConfigureAwait(false); - } + onItemFound, cancellationToken); - private async ValueTask WriteBatchToPipeAsync( + 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 diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs index f2983fc890c92..22b79b01d17a3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigateToSearch/RemoteNavigateToSearchService.cs @@ -30,15 +30,14 @@ public RemoteNavigateToSearchService(in ServiceConstructionArguments arguments, _callback = callback; } - private (Func, ValueTask> onItemsFound, Func onProjectCompleted) GetCallbacks( + private (Func, Task> onItemsFound, Func onProjectCompleted) GetCallbacks( RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { - Func, ValueTask> onItemsFound = - async i => await _callback.InvokeAsync((callback, cancellationToken) => - callback.OnItemsFoundAsync(callbackId, i), - cancellationToken).ConfigureAwait(false); + 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); From eaf2bc399fc7bb726de707f3384efdbcf6d8684d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:06:21 -0700 Subject: [PATCH 0973/1047] simplify --- .../NavigateTo/AbstractNavigateToSearchService.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 97d90bcdddd58..f97500cdc1893 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -92,7 +92,7 @@ private static IEnumerable Prioritize(IEnumerable items, Func /// Parallel.ForEachAsync, allowing for parallel processing of the items, with a preference towards /// earlier items. /// - private static async Task PerformParallelSearchAsync( + private static Task PerformParallelSearchAsync( IEnumerable items, Func, ValueTask> callback, Func, Task> onItemsFound, @@ -102,15 +102,9 @@ private static async Task PerformParallelSearchAsync( // Concurrently, the reading task will grab items when available, and send them over to the host. var channel = Channel.CreateUnbounded(s_channelOptions); - await channel.RunProducerConsumerAsync( - PerformSearchAsync, + return channel.RunProducerConsumerAsync( + onItemFound => RoslynParallel.ForEachAsync(items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)), onItemsFound, - cancellationToken).ConfigureAwait(false); - - return; - - Task PerformSearchAsync(Action onItemFound) - => RoslynParallel.ForEachAsync( - items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)); + cancellationToken); } } From ae4637e6d80035769e8e8d1e22a2fc188f16451b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:09:54 -0700 Subject: [PATCH 0974/1047] simplify --- .../AbstractNavigateToSearchService.cs | 4 +- .../FindReferencesSearchEngine.cs | 4 +- .../Shared/Extensions/ChannelExtensions.cs | 66 +++++++++---------- .../Remote/Core/RemoteHostAssetWriter.cs | 10 +-- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index f97500cdc1893..fdb21045b4e88 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -103,8 +103,8 @@ private static Task PerformParallelSearchAsync( var channel = Channel.CreateUnbounded(s_channelOptions); return channel.RunProducerConsumerAsync( - onItemFound => RoslynParallel.ForEachAsync(items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)), - onItemsFound, + produceItemsAsync: onItemFound => RoslynParallel.ForEachAsync(items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)), + consumeItemsAsync: onItemsFound, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 80f745db995ef..08897502227d5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -82,8 +82,8 @@ public async Task FindReferencesAsync( try { await channel.RunProducerConsumerAsync( - PerformSearchAsync, - async references => await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false), + produceItemsAsync: PerformSearchAsync, + consumeItemsAsync: async references => await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } finally diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index 78be88081bb60..5151e1224e477 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -19,30 +19,30 @@ internal static class ChannelExtensions /// Version of when caller the prefers the results being pre-packaged into arrays /// to process. /// - public static Task RunProducerConsumerAsync( - this Channel channel, - Func, Task> produceElementsAsync, - Func, Task> consumeElementsAsync, + public static Task RunProducerConsumerAsync( + this Channel channel, + Func, Task> produceItemsAsync, + Func, Task> consumeItemsAsync, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( channel, - produceElementsAsync, - ConsumeElementsAsArrayAsync, + produceItemsAsync, + ConsumeItemsAsArrayAsync, cancellationToken); - async Task ConsumeElementsAsArrayAsync(ChannelReader reader) + async Task ConsumeItemsAsArrayAsync(ChannelReader reader) { - using var _ = ArrayBuilder.GetInstance(out var batch); + 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 (channel.Reader.TryRead(out var item)) - batch.Add(item); + items.Add(item); - await consumeElementsAsync(batch.ToImmutableAndClear()).ConfigureAwait(false); + await consumeItemsAsync(items.ToImmutableAndClear()).ConfigureAwait(false); } } } @@ -50,20 +50,20 @@ async Task ConsumeElementsAsArrayAsync(ChannelReader reader) /// /// Version of when the caller prefers working with a stream of results. /// - public static Task RunProducerConsumerAsync( - this Channel channel, - Func, Task> produceElementsAsync, - Func, Task> consumeElementsAsync, + public static Task RunProducerConsumerAsync( + this Channel channel, + Func, Task> produceItemsAsync, + Func, Task> consumeItemsAsync, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( channel, - produceElementsAsync, - ConsumeElementsAsStreamAsync, + produceItemsAsync, + ConsumeItemsAsStreamAsync, cancellationToken); - Task ConsumeElementsAsStreamAsync(ChannelReader reader) - => consumeElementsAsync(reader.ReadAllAsync(cancellationToken)); + Task ConsumeItemsAsStreamAsync(ChannelReader reader) + => consumeItemsAsync(reader.ReadAllAsync(cancellationToken)); } /// @@ -72,49 +72,49 @@ Task ConsumeElementsAsStreamAsync(ChannelReader reader) /// around the routines. Importantly, it 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 elements. It will be passed an action that can be used to write elements to the + /// 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 elements. + /// is the routine called to consume the items. /// - private static async Task RunProducerConsumerImplAsync( - this Channel channel, - Func, Task> produceElementsAsync, - Func, Task> consumeElementsAsync, + private static async Task RunProducerConsumerImplAsync( + this Channel channel, + Func, Task> produceItemsAsync, + Func, Task> consumeItemsAsync, CancellationToken cancellationToken) { - // When cancellation happens, attempt to close the channel. That will unblock the task processing the elements. + // 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)), + static (obj, cancellationToken) => ((Channel)obj!).Writer.TryComplete(new OperationCanceledException(cancellationToken)), state: channel); #else () => channel.Writer.TryComplete(new OperationCanceledException(cancellationToken))); #endif await Task.WhenAll( - ProduceElementsAndWriteToChannelAsync(), - ReadFromChannelAndConsumeElementsAsync()).ConfigureAwait(false); + ProduceItemsAndWriteToChannelAsync(), + ReadFromChannelAndConsumeItemsAsync()).ConfigureAwait(false); return; - async Task ReadFromChannelAndConsumeElementsAsync() + async Task ReadFromChannelAndConsumeItemsAsync() { await Task.Yield().ConfigureAwait(false); - await consumeElementsAsync(channel.Reader).ConfigureAwait(false); + await consumeItemsAsync(channel.Reader).ConfigureAwait(false); } - async Task ProduceElementsAndWriteToChannelAsync() + async Task ProduceItemsAndWriteToChannelAsync() { Exception? exception = null; try { await Task.Yield().ConfigureAwait(false); - await produceElementsAsync(item => channel.Writer.TryWrite(item)).ConfigureAwait(false); + await produceItemsAsync(item => channel.Writer.TryWrite(item)).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index b577a9291555d..a28c4f49857b3 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -80,7 +80,7 @@ internal readonly struct RemoteHostAssetWriter( private readonly ReadOnlyMemory _checksums = checksums; private readonly ISerializerService _serializer = serializer; - public async ValueTask WriteDataAsync(CancellationToken cancellationToken) + public Task WriteDataAsync(CancellationToken cancellationToken) { // Create a channel to communicate between the searching and writing tasks. This allows the searching task to // find items, add them to the channel synchronously, and immediately continue searching for more items. @@ -88,10 +88,10 @@ public async ValueTask WriteDataAsync(CancellationToken cancellationToken) var channel = Channel.CreateUnbounded(s_channelOptions); var @this = this; - await channel.RunProducerConsumerAsync( - onItemFound => @this.FindAssetsAsync(onItemFound, cancellationToken), - items => @this.WriteBatchToPipeAsync(items, cancellationToken), - cancellationToken).ConfigureAwait(false); + return channel.RunProducerConsumerAsync( + produceItemsAsync: onItemFound => @this.FindAssetsAsync(onItemFound, cancellationToken), + consumeItemsAsync: items => @this.WriteBatchToPipeAsync(items, cancellationToken), + cancellationToken); } private Task FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) From b217891ff399b18047b3c0f2a716069cacd37397 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:14:21 -0700 Subject: [PATCH 0975/1047] simplify --- .../Core/Portable/Shared/Extensions/ChannelExtensions.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index 5151e1224e477..2af9036189f4a 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -59,11 +59,8 @@ public static Task RunProducerConsumerAsync( return RunProducerConsumerImplAsync( channel, produceItemsAsync, - ConsumeItemsAsStreamAsync, + reader => consumeItemsAsync(reader.ReadAllAsync(cancellationToken)), cancellationToken); - - Task ConsumeItemsAsStreamAsync(ChannelReader reader) - => consumeItemsAsync(reader.ReadAllAsync(cancellationToken)); } /// From 68baa954553f805b111c438e4ad7219fd6e02f6d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:29:00 -0700 Subject: [PATCH 0976/1047] Strong types and static lambdas --- .../AbstractNavigateToSearchService.cs | 5 +- .../FindReferencesSearchEngine.cs | 65 ++++++++++--------- .../Shared/Extensions/ChannelExtensions.cs | 49 ++++++++------ .../Remote/Core/RemoteHostAssetWriter.cs | 6 +- 4 files changed, 69 insertions(+), 56 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index fdb21045b4e88..52d26e973eced 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -103,8 +103,9 @@ private static Task PerformParallelSearchAsync( var channel = Channel.CreateUnbounded(s_channelOptions); return channel.RunProducerConsumerAsync( - produceItemsAsync: onItemFound => RoslynParallel.ForEachAsync(items, cancellationToken, (item, cancellationToken) => callback(item, onItemFound)), - consumeItemsAsync: onItemsFound, + 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/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 08897502227d5..5f1ee6899ed21 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -82,8 +82,9 @@ public async Task FindReferencesAsync( try { await channel.RunProducerConsumerAsync( - produceItemsAsync: PerformSearchAsync, - consumeItemsAsync: async references => await _progress.OnReferencesFoundAsync(references, cancellationToken).ConfigureAwait(false), + 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 @@ -92,42 +93,44 @@ await channel.RunProducerConsumerAsync( } return; + } - async Task PerformSearchAsync(Action onReferenceFound) - { - var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); - unifiedSymbols.AddRange(symbols); + private async Task PerformSearchAsync( + ImmutableArray symbols, Action onReferenceFound, CancellationToken cancellationToken) + { + var unifiedSymbols = new MetadataUnifyingSymbolHashSet(); + unifiedSymbols.AddRange(symbols); - var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposable.ConfigureAwait(false); + var disposable = await _progressTracker.AddSingleItemAsync(cancellationToken).ConfigureAwait(false); + await using var _ = disposable.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); + // 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); - // Report the initial set of symbols to the caller. - var allSymbols = symbolSet.GetAllSymbols(); - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + // Report the initial set of symbols to the caller. + var allSymbols = symbolSet.GetAllSymbols(); + await ReportGroupsAsync(allSymbols, cancellationToken).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); + // 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); - // 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. - await _progressTracker.AddItemsAsync(projectsToSearch.Length, 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. + await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); - // 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), - cancellationToken, - async (tuple, cancellationToken) => await ProcessProjectAsync( - tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); - } + // 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), + cancellationToken, + async (tuple, cancellationToken) => await ProcessProjectAsync( + tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); + return; async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( SymbolSet symbolSet, diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index 2af9036189f4a..748e70428ef0c 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -19,19 +19,25 @@ internal static class ChannelExtensions /// Version of when caller the prefers the results being pre-packaged into arrays /// to process. /// - public static Task RunProducerConsumerAsync( + public static Task RunProducerConsumerAsync( this Channel channel, - Func, Task> produceItemsAsync, - Func, Task> consumeItemsAsync, + Func, TArgs, Task> produceItems, + Func, TArgs, Task> consumeItems, + TArgs args, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( channel, - produceItemsAsync, - ConsumeItemsAsArrayAsync, + static (onItemFound, args) => args.produceItems(onItemFound, args.args), + static (reader, args) => ConsumeItemsAsArrayAsync(reader, args.consumeItems, args.args, args.cancellationToken), + (produceItems, consumeItems, args, cancellationToken), cancellationToken); - async Task ConsumeItemsAsArrayAsync(ChannelReader reader) + static async Task ConsumeItemsAsArrayAsync( + ChannelReader reader, + Func, TArgs, Task> consumeItems, + TArgs args, + CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var items); @@ -39,10 +45,10 @@ async Task ConsumeItemsAsArrayAsync(ChannelReader reader) { // 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 (channel.Reader.TryRead(out var item)) + while (reader.TryRead(out var item)) items.Add(item); - await consumeItemsAsync(items.ToImmutableAndClear()).ConfigureAwait(false); + await consumeItems(items.ToImmutableAndClear(), args).ConfigureAwait(false); } } } @@ -50,16 +56,18 @@ async Task ConsumeItemsAsArrayAsync(ChannelReader reader) /// /// Version of when the caller prefers working with a stream of results. /// - public static Task RunProducerConsumerAsync( + public static Task RunProducerConsumerAsync( this Channel channel, - Func, Task> produceItemsAsync, - Func, Task> consumeItemsAsync, + Func, TArgs, Task> produceItems, + Func, TArgs, Task> consumeItems, + TArgs args, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( channel, - produceItemsAsync, - reader => consumeItemsAsync(reader.ReadAllAsync(cancellationToken)), + static (onItemFound, args) => args.produceItems(onItemFound, args.args), + static (reader, args) => args.consumeItems(reader.ReadAllAsync(args.cancellationToken), args.args), + (produceItems, consumeItems, args, cancellationToken), cancellationToken); } @@ -69,18 +77,19 @@ public static Task RunProducerConsumerAsync( /// around the routines. Importantly, it handles backpressure, ensuring that if the consumption routine cannot keep /// up, that the production routine will be throttled. /// - /// is the routine + /// 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. + /// is the routine called to consume the items. /// - private static async Task RunProducerConsumerImplAsync( + private static async Task RunProducerConsumerImplAsync( this Channel channel, - Func, Task> produceItemsAsync, - Func, Task> consumeItemsAsync, + Func, TArgs, Task> produceItems, + Func, TArgs, Task> consumeItems, + TArgs args, CancellationToken cancellationToken) { // When cancellation happens, attempt to close the channel. That will unblock the task processing the items. @@ -102,7 +111,7 @@ await Task.WhenAll( async Task ReadFromChannelAndConsumeItemsAsync() { await Task.Yield().ConfigureAwait(false); - await consumeItemsAsync(channel.Reader).ConfigureAwait(false); + await consumeItems(channel.Reader, args).ConfigureAwait(false); } async Task ProduceItemsAndWriteToChannelAsync() @@ -111,7 +120,7 @@ async Task ProduceItemsAndWriteToChannelAsync() try { await Task.Yield().ConfigureAwait(false); - await produceItemsAsync(item => channel.Writer.TryWrite(item)).ConfigureAwait(false); + await produceItems(item => channel.Writer.TryWrite(item), args).ConfigureAwait(false); } catch (Exception ex) when ((exception = ex) == null) { diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index a28c4f49857b3..c7e2f03b8a7d2 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -87,10 +87,10 @@ public Task WriteDataAsync(CancellationToken cancellationToken) // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. var channel = Channel.CreateUnbounded(s_channelOptions); - var @this = this; return channel.RunProducerConsumerAsync( - produceItemsAsync: onItemFound => @this.FindAssetsAsync(onItemFound, cancellationToken), - consumeItemsAsync: items => @this.WriteBatchToPipeAsync(items, cancellationToken), + 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); } From c5eb73a510cfd6a6062f04554f2be3b42d1b25e3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:30:55 -0700 Subject: [PATCH 0977/1047] simplify --- .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 5f1ee6899ed21..e09aca9a4f5d3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -91,8 +91,6 @@ await channel.RunProducerConsumerAsync( { await _progress.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } - - return; } private async Task PerformSearchAsync( From 0c82146c2e4f1cdb41ee8eda067426610dcf1c7e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 11:31:44 -0700 Subject: [PATCH 0978/1047] simplify --- .../FindReferencesSearchEngine.cs | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index e09aca9a4f5d3..c6ac8b627f0fe 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -128,32 +128,31 @@ await RoslynParallel.ForEachAsync( cancellationToken, async (tuple, cancellationToken) => await ProcessProjectAsync( tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); - return; + } - async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( - SymbolSet symbolSet, - ImmutableArray projectsToSearch, - [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable<(Project project, ImmutableArray allSymbols)> GetProjectsAndSymbolsToSearchAsync( + SymbolSet symbolSet, + ImmutableArray projectsToSearch, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + var dependencyGraph = _solution.GetProjectDependencyGraph(); + foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) { - var dependencyGraph = _solution.GetProjectDependencyGraph(); - foreach (var projectId in dependencyGraph.GetTopologicallySortedProjects(cancellationToken)) - { - var currentProject = _solution.GetRequiredProject(projectId); - if (!projectsToSearch.Contains(currentProject)) - continue; + 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(); + // 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(); - // Report any new symbols we've cascaded to to our caller. - await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); + // Report any new symbols we've cascaded to to our caller. + await ReportGroupsAsync(allSymbols, cancellationToken).ConfigureAwait(false); - yield return (currentProject, allSymbols); - } + yield return (currentProject, allSymbols); } } From 24890c15f6f71a379e3e3919775d909bf8911fa3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 12:23:02 -0700 Subject: [PATCH 0979/1047] Move into manager --- .../AbstractNavigateToSearchService.cs | 7 ++----- .../FindReferencesSearchEngine.cs | 5 ++--- .../Shared/Extensions/ChannelExtensions.cs | 20 ++++++++++--------- .../Remote/Core/RemoteHostAssetWriter.cs | 7 ++----- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 52d26e973eced..67d2a2b645d23 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -97,15 +97,12 @@ private static Task PerformParallelSearchAsync( Func, ValueTask> callback, Func, Task> onItemsFound, CancellationToken cancellationToken) - { // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. // Concurrently, the reading task will grab items when available, and send them over to the host. - var channel = Channel.CreateUnbounded(s_channelOptions); - - return channel.RunProducerConsumerAsync( + => ChannelManager.RunProducerConsumerAsync( + s_channelOptions, 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/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index c6ac8b627f0fe..7fddb21a5fd1c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -76,12 +76,11 @@ public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationTo public async Task FindReferencesAsync( ImmutableArray symbols, CancellationToken cancellationToken) { - var channel = Channel.CreateUnbounded(s_channelOptions); - await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - await channel.RunProducerConsumerAsync( + await ChannelManager.RunProducerConsumerAsync( + s_channelOptions, 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), diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs index 748e70428ef0c..199e35a0fb9a3 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs @@ -13,21 +13,21 @@ namespace Microsoft.CodeAnalysis.Shared.Extensions; -internal static class ChannelExtensions +internal static class ChannelManager { /// /// Version of when caller the prefers the results being pre-packaged into arrays /// to process. /// - public static Task RunProducerConsumerAsync( - this Channel channel, + public static Task RunProducerConsumerAsync( + UnboundedChannelOptions channelOptions, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, TArgs args, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( - channel, + channelOptions, static (onItemFound, args) => args.produceItems(onItemFound, args.args), static (reader, args) => ConsumeItemsAsArrayAsync(reader, args.consumeItems, args.args, args.cancellationToken), (produceItems, consumeItems, args, cancellationToken), @@ -56,15 +56,15 @@ static async Task ConsumeItemsAsArrayAsync( /// /// Version of when the caller prefers working with a stream of results. /// - public static Task RunProducerConsumerAsync( - this Channel channel, + public static Task RunProducerConsumerAsync( + UnboundedChannelOptions channelOptions, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, TArgs args, CancellationToken cancellationToken) { return RunProducerConsumerImplAsync( - channel, + channelOptions, static (onItemFound, args) => args.produceItems(onItemFound, args.args), static (reader, args) => args.consumeItems(reader.ReadAllAsync(args.cancellationToken), args.args), (produceItems, consumeItems, args, cancellationToken), @@ -85,13 +85,15 @@ public static Task RunProducerConsumerAsync( /// /// is the routine called to consume the items. /// - private static async Task RunProducerConsumerImplAsync( - this Channel channel, + private static async Task RunProducerConsumerImplAsync( + UnboundedChannelOptions channelOptions, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, TArgs args, CancellationToken cancellationToken) { + var channel = Channel.CreateUnbounded(channelOptions); + // 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( diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index c7e2f03b8a7d2..fd04d1d8822e7 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -81,18 +81,15 @@ internal readonly struct RemoteHostAssetWriter( private readonly ISerializerService _serializer = serializer; public Task WriteDataAsync(CancellationToken cancellationToken) - { // Create a channel to communicate between the searching and writing tasks. This allows the searching task to // find items, add them to the channel synchronously, and immediately continue searching for more items. // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. - var channel = Channel.CreateUnbounded(s_channelOptions); - - return channel.RunProducerConsumerAsync( + => ChannelManager.RunProducerConsumerAsync( + s_channelOptions, 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( From 4c61f86064240141464674ef231e3f01e6f920f2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 12:31:26 -0700 Subject: [PATCH 0980/1047] Simplify --- .../FindReferences/FindReferencesSearchEngine.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 7fddb21a5fd1c..7fd8e2e12dfb6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -245,11 +245,8 @@ await finder.DetermineDocumentsToSearchAsync( await RoslynParallel.ForEachAsync( documentToSymbols, cancellationToken, - async (kvp, cancellationToken) => - { - var (document, docSymbols) = kvp; - await ProcessDocumentAsync(document, docSymbols, symbolToGlobalAliases, onReferenceFound, cancellationToken).ConfigureAwait(false); - }).ConfigureAwait(false); + (kvp, cancellationToken) => + ProcessDocumentAsync(kvp.Key, kvp.Value, symbolToGlobalAliases, onReferenceFound, cancellationToken)).ConfigureAwait(false); } finally { @@ -279,7 +276,7 @@ static MetadataUnifyingSymbolHashSet GetSymbolSet(PooledDictionary? 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, @@ -340,7 +337,7 @@ async Task ProcessDocumentAsync( { await finder.FindReferencesInDocumentAsync( symbol, state, - (loc, tuple) => tuple.onReferenceFound((tuple.group, tuple.symbol, loc.Location)), + static (loc, tuple) => tuple.onReferenceFound((tuple.group, tuple.symbol, loc.Location)), (group, symbol, onReferenceFound), _options, cancellationToken).ConfigureAwait(false); From da49c48157624abcc48a291f3d4518a1618c7c4a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 12:36:01 -0700 Subject: [PATCH 0981/1047] rename --- .../Portable/NavigateTo/AbstractNavigateToSearchService.cs | 1 - .../ChannelExtensions.cs => Utilities/ChannelManager.cs} | 3 ++- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Workspaces/Core/Portable/Shared/{Extensions/ChannelExtensions.cs => Utilities/ChannelManager.cs} (98%) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 67d2a2b645d23..ecb925b0f9336 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -9,7 +9,6 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ChannelManager.cs similarity index 98% rename from src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs rename to src/Workspaces/Core/Portable/Shared/Utilities/ChannelManager.cs index 199e35a0fb9a3..6e3411c7c1501 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ChannelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ChannelManager.cs @@ -9,9 +9,10 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Shared.Extensions; +namespace Microsoft.CodeAnalysis.Shared.Utilities; internal static class ChannelManager { diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index fd04d1d8822e7..a686e0fc2b1f9 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; -using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; From f239fb3ef1f21113f6e357c038ad0e73d6a8929e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 12:41:23 -0700 Subject: [PATCH 0982/1047] rename --- .../Portable/NavigateTo/AbstractNavigateToSearchService.cs | 2 +- .../FindReferences/FindReferencesSearchEngine.cs | 2 +- .../Utilities/{ChannelManager.cs => ProducerConsumer.cs} | 6 +++--- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/Workspaces/Core/Portable/Shared/Utilities/{ChannelManager.cs => ProducerConsumer.cs} (97%) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index ecb925b0f9336..d826bf23aa22c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -98,7 +98,7 @@ private static Task PerformParallelSearchAsync( CancellationToken cancellationToken) // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. // Concurrently, the reading task will grab items when available, and send them over to the host. - => ChannelManager.RunProducerConsumerAsync( + => ProducerConsumer.RunUnboundedAsync( s_channelOptions, produceItems: static (onItemFound, args) => RoslynParallel.ForEachAsync(args.items, args.cancellationToken, (item, cancellationToken) => args.callback(item, onItemFound)), consumeItems: static (items, args) => args.onItemsFound(items), diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 7fd8e2e12dfb6..2b923a430e09b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -79,7 +79,7 @@ public async Task FindReferencesAsync( await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - await ChannelManager.RunProducerConsumerAsync( + await ProducerConsumer.RunUnboundedAsync( s_channelOptions, 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), diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ChannelManager.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs similarity index 97% rename from src/Workspaces/Core/Portable/Shared/Utilities/ChannelManager.cs rename to src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index 6e3411c7c1501..8d15e2ab65bfb 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ChannelManager.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -14,13 +14,13 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities; -internal static class ChannelManager +internal static class ProducerConsumer { /// /// Version of when caller the prefers the results being pre-packaged into arrays /// to process. /// - public static Task RunProducerConsumerAsync( + public static Task RunUnboundedAsync( UnboundedChannelOptions channelOptions, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, @@ -57,7 +57,7 @@ static async Task ConsumeItemsAsArrayAsync( /// /// Version of when the caller prefers working with a stream of results. /// - public static Task RunProducerConsumerAsync( + public static Task RunUnboundedAsync( UnboundedChannelOptions channelOptions, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index a686e0fc2b1f9..5bb006bfd1860 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -84,7 +84,7 @@ public Task WriteDataAsync(CancellationToken cancellationToken) // Create a channel to communicate between the searching and writing tasks. This allows the searching task to // find items, add them to the channel synchronously, and immediately continue searching for more items. // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. - => ChannelManager.RunProducerConsumerAsync( + => ProducerConsumer.RunUnboundedAsync( s_channelOptions, produceItems: static (onItemFound, args) => args.@this.FindAssetsAsync(onItemFound, args.cancellationToken), consumeItems: static (items, args) => args.@this.WriteBatchToPipeAsync(items, args.cancellationToken), From 8552a5e6b90dffe3f41b6a06de8bb6ae88234e38 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 12:52:31 -0700 Subject: [PATCH 0983/1047] remove comment --- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 5bb006bfd1860..17829d2a1dbb4 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -94,10 +94,6 @@ public Task WriteDataAsync(CancellationToken cancellationToken) private Task FindAssetsAsync(Action onItemFound, CancellationToken cancellationToken) => _scope.FindAssetsAsync( _assetPath, _checksums, - // 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 FindAssetsAsync completes or throws an exception) or if - // the cancellationToken is triggered above in WriteDataAsync. 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. static (checksum, asset, onItemFound) => onItemFound((checksum, asset)), onItemFound, cancellationToken); From 19acb37330c0b5f05f3ab8cc511b32706fbde128 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 12:53:59 -0700 Subject: [PATCH 0984/1047] Move comment --- .../Shared/Utilities/ProducerConsumer.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index 8d15e2ab65bfb..f19bc7c6f8678 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -17,8 +17,7 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities; internal static class ProducerConsumer { /// - /// Version of when caller the prefers the results being pre-packaged into arrays - /// to process. + /// Version of when caller the prefers the results being pre-packaged into arrays to process. /// public static Task RunUnboundedAsync( UnboundedChannelOptions channelOptions, @@ -27,7 +26,7 @@ public static Task RunUnboundedAsync( TArgs args, CancellationToken cancellationToken) { - return RunProducerConsumerImplAsync( + return RunAsync( channelOptions, static (onItemFound, args) => args.produceItems(onItemFound, args.args), static (reader, args) => ConsumeItemsAsArrayAsync(reader, args.consumeItems, args.args, args.cancellationToken), @@ -55,7 +54,7 @@ static async Task ConsumeItemsAsArrayAsync( } /// - /// Version of when the caller prefers working with a stream of results. + /// Version of when the caller prefers working with a stream of results. /// public static Task RunUnboundedAsync( UnboundedChannelOptions channelOptions, @@ -64,7 +63,7 @@ public static Task RunUnboundedAsync( TArgs args, CancellationToken cancellationToken) { - return RunProducerConsumerImplAsync( + return RunAsync( channelOptions, static (onItemFound, args) => args.produceItems(onItemFound, args.args), static (reader, args) => args.consumeItems(reader.ReadAllAsync(args.cancellationToken), args.args), @@ -86,7 +85,7 @@ public static Task RunUnboundedAsync( /// /// is the routine called to consume the items. /// - private static async Task RunProducerConsumerImplAsync( + private static async Task RunAsync( UnboundedChannelOptions channelOptions, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, @@ -123,6 +122,11 @@ async Task ProduceItemsAndWriteToChannelAsync() 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) From 73a1eec17026fcd545fbc6e4e4e188e598760df1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 13:22:49 -0700 Subject: [PATCH 0985/1047] Docs --- .../Core/Portable/Shared/Utilities/ProducerConsumer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index f19bc7c6f8678..82904595cccd5 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -73,9 +73,10 @@ public static Task RunUnboundedAsync( /// /// Helper utility for the pattern of a pair of a production routine and consumption routine using a channel to - /// coordinate data transfer. The channel itself is provided by the caller, but manages the rules and behaviors - /// around the routines. Importantly, it handles backpressure, ensuring that if the consumption routine cannot keep - /// up, that the production routine will be throttled. + /// coordinate data transfer. The channel options are provided by the caller. Those options will be used to create + /// a , which will then then manage the rules and behaviors around the routines. + /// Importantly, it 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 From 7b316bbc29a73f2924c42fa7c38005c78a1b3304 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 13:24:12 -0700 Subject: [PATCH 0986/1047] Docs --- .../Core/Portable/Shared/Utilities/ProducerConsumer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index 82904595cccd5..932e005acbd90 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -73,10 +73,10 @@ public static Task RunUnboundedAsync( /// /// Helper utility for the pattern of a pair of a production routine and consumption routine using a channel to - /// coordinate data transfer. The channel options are provided by the caller. Those options will be used to create - /// a , which will then then manage the rules and behaviors around the routines. - /// Importantly, it handles backpressure, ensuring that if the consumption routine cannot keep up, that the - /// production routine will be throttled. + /// 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 From 9cd69fab67e8a73aa2ecebc8510f948477862ab3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 13:25:33 -0700 Subject: [PATCH 0987/1047] Docs --- .../Portable/Shared/Utilities/ProducerConsumer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index 932e005acbd90..cf811b7e785e5 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -78,13 +78,13 @@ public static Task RunUnboundedAsync( /// 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 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. + /// 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 RunAsync( UnboundedChannelOptions channelOptions, From 83d33d30f59cceacd370dd7e0e448268dffffc7b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 13:40:43 -0700 Subject: [PATCH 0988/1047] Move options --- .../AbstractNavigateToSearchService.cs | 8 ++-- .../FindReferencesSearchEngine.cs | 5 +-- .../Shared/Utilities/ProducerConsumer.cs | 44 +++++++++++++------ .../Remote/Core/RemoteHostAssetWriter.cs | 26 +++++------ 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index d826bf23aa22c..aab8349f3bb92 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -16,8 +16,6 @@ namespace Microsoft.CodeAnalysis.NavigateTo; internal abstract partial class AbstractNavigateToSearchService : IAdvancedNavigateToSearchService { - private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; - public static readonly IImmutableSet AllKinds = [ NavigateToItemKind.Class, NavigateToItemKind.Constant, @@ -96,10 +94,10 @@ private static Task PerformParallelSearchAsync( Func, ValueTask> callback, Func, Task> onItemsFound, CancellationToken cancellationToken) - // Use an unbounded channel to allow the writing work to write as many items as it can find without blocking. + // Use the ProducerConsumer<> to allow the writing work to write as many items as it can find without blocking. // Concurrently, the reading task will grab items when available, and send them over to the host. - => ProducerConsumer.RunUnboundedAsync( - s_channelOptions, + => 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), diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 2b923a430e09b..6a98f1b4739ce 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -25,7 +25,6 @@ namespace Microsoft.CodeAnalysis.FindSymbols; internal partial class FindReferencesSearchEngine { private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); - private static readonly UnboundedChannelOptions s_channelOptions = new() { SingleReader = true }; private readonly Solution _solution; private readonly IImmutableSet? _documents; @@ -79,8 +78,8 @@ public async Task FindReferencesAsync( await _progress.OnStartedAsync(cancellationToken).ConfigureAwait(false); try { - await ProducerConsumer.RunUnboundedAsync( - s_channelOptions, + 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), diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index cf811b7e785e5..5e2b79d885e68 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -14,20 +14,32 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities; +internal readonly record struct ProducerConsumerOptions +{ + public static readonly ProducerConsumerOptions SingleReaderOptions = new() { SingleReader = true }; + 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. + /// Version of when caller the prefers the results being pre-packaged into arrays to process. /// - public static Task RunUnboundedAsync( - UnboundedChannelOptions channelOptions, + public static Task RunAsync( + ProducerConsumerOptions options, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, TArgs args, CancellationToken cancellationToken) { - return RunAsync( - channelOptions, + 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), @@ -54,17 +66,17 @@ static async Task ConsumeItemsAsArrayAsync( } /// - /// Version of when the caller prefers working with a stream of results. + /// Version of when the caller prefers working with a stream of results. /// - public static Task RunUnboundedAsync( - UnboundedChannelOptions channelOptions, + public static Task RunAsync( + ProducerConsumerOptions options, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, TArgs args, CancellationToken cancellationToken) { - return RunAsync( - channelOptions, + 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), @@ -73,7 +85,7 @@ public static Task RunUnboundedAsync( /// /// 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 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. @@ -86,14 +98,18 @@ public static Task RunUnboundedAsync( /// 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 RunAsync( - UnboundedChannelOptions channelOptions, + private static async Task RunImplAsync( + ProducerConsumerOptions options, Func, TArgs, Task> produceItems, Func, TArgs, Task> consumeItems, TArgs args, CancellationToken cancellationToken) { - var channel = Channel.CreateUnbounded(channelOptions); + 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. diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 17829d2a1dbb4..bb3b75464252a 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -63,17 +63,6 @@ internal readonly struct RemoteHostAssetWriter( private static readonly ObjectPool s_streamPool = new(() => new()); - private static readonly UnboundedChannelOptions s_channelOptions = new() - { - // We have a single task reading the data from the channel and writing it to the pipe. This option allows the - // channel to operate in a more efficient manner knowing it won't have to synchronize data for multiple readers. - SingleReader = true, - - // Currently we only have a single writer writing to the channel when we call _scope.FindAssetsAsync. However, - // we could change this in the future to allow the search to happen in parallel. - SingleWriter = true, - }; - private readonly PipeWriter _pipeWriter = pipeWriter; private readonly Scope _scope = scope; private readonly AssetPath _assetPath = assetPath; @@ -81,11 +70,16 @@ internal readonly struct RemoteHostAssetWriter( private readonly ISerializerService _serializer = serializer; public Task WriteDataAsync(CancellationToken cancellationToken) - // Create a channel to communicate between the searching and writing tasks. This allows the searching task to - // find items, add them to the channel synchronously, and immediately continue searching for more items. - // Concurrently, the writing task can read from the channel and write the items to the pipe-writer. - => ProducerConsumer.RunUnboundedAsync( - s_channelOptions, + // Use the ProducderConsumer<> to communicate between the searching and writing tasks. This allows the + // searching task to find items, add them to the channel synchronously, and immediately continue searching for + // more items. Concurrently, the writing task can read from the channel and write the items to the pipe-writer. + => ProducerConsumer.RunAsync( + // We have a single task reading the data from the channel and writing it to the pipe. This option allows + // the producer-consumer to operate in a more efficient manner knowing it won't have to synchronize data for + // multiple readers. Currently we only have a single writer writing to the channel when we call + // _scope.FindAssetsAsync. However, we could change this in the future to allow the search to happen in + // parallel. + 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), From 20cb023ffe45c52f1caafdb040cd7d1f4b7b6e3f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 13:42:58 -0700 Subject: [PATCH 0989/1047] Comments --- .../NavigateTo/AbstractNavigateToSearchService.cs | 2 -- .../Core/Portable/Shared/Utilities/ProducerConsumer.cs | 9 +++++++++ src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 8 -------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index aab8349f3bb92..a2755b17e4500 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -94,8 +94,6 @@ private static Task PerformParallelSearchAsync( Func, ValueTask> callback, Func, Task> onItemsFound, CancellationToken cancellationToken) - // Use the ProducerConsumer<> to allow the writing work to write as many items as it can find without blocking. - // Concurrently, the reading task will grab items when available, and send them over to the host. => ProducerConsumer.RunAsync( ProducerConsumerOptions.SingleReaderOptions, produceItems: static (onItemFound, args) => RoslynParallel.ForEachAsync(args.items, args.cancellationToken, (item, cancellationToken) => args.callback(item, onItemFound)), diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs index 5e2b79d885e68..2de076fc5cbd0 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/ProducerConsumer.cs @@ -16,7 +16,16 @@ 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 }; /// diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index bb3b75464252a..8cdea8582cb14 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -70,15 +70,7 @@ internal readonly struct RemoteHostAssetWriter( private readonly ISerializerService _serializer = serializer; public Task WriteDataAsync(CancellationToken cancellationToken) - // Use the ProducderConsumer<> to communicate between the searching and writing tasks. This allows the - // searching task to find items, add them to the channel synchronously, and immediately continue searching for - // more items. Concurrently, the writing task can read from the channel and write the items to the pipe-writer. => ProducerConsumer.RunAsync( - // We have a single task reading the data from the channel and writing it to the pipe. This option allows - // the producer-consumer to operate in a more efficient manner knowing it won't have to synchronize data for - // multiple readers. Currently we only have a single writer writing to the channel when we call - // _scope.FindAssetsAsync. However, we could change this in the future to allow the search to happen in - // parallel. ProducerConsumerOptions.SingleReaderWriterOptions, produceItems: static (onItemFound, args) => args.@this.FindAssetsAsync(onItemFound, args.cancellationToken), consumeItems: static (items, args) => args.@this.WriteBatchToPipeAsync(items, args.cancellationToken), From 4e6311838e10eeac252db642303536ffadb0bee8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 13:44:12 -0700 Subject: [PATCH 0990/1047] cleanup --- .../Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs | 1 - .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 1 - src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index a2755b17e4500..4117157b3655d 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -6,7 +6,6 @@ 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.Utilities; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 6a98f1b4739ce..dc39408d7eaf4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindSymbols.Finders; using Microsoft.CodeAnalysis.Internal.Log; diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index 8cdea8582cb14..a43e259eab20a 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.IO.Pipelines; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; From 3f43d8b44084e062846c447af02c14f3f1921aaf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 20:18:18 -0700 Subject: [PATCH 0991/1047] Cleanup --- .../FindReferencesSearchEngine.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index dc39408d7eaf4..b6c20e2a4d0d9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -260,15 +260,7 @@ await RoslynParallel.ForEachAsync( } 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 _ => s_metadataUnifyingSymbolHashSetPool.Allocate()); } private static PooledHashSet? TryGet(Dictionary> dictionary, T key) where T : notnull @@ -366,15 +358,7 @@ private async Task AddGlobalAliasesAsync( } 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) { From b116d27bd229104ff1184ed85b5af08df1ac4631 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 20:58:37 -0700 Subject: [PATCH 0992/1047] parallel options --- .../FindReferencesSearchEngine.cs | 68 ++++++++----------- ...sSearchEngine_FindReferencesInDocuments.cs | 4 +- .../MetadataUnifyingSymbolHashSet.cs | 12 ++++ .../Shared/Utilities/RoslynParallel.cs | 51 +++++++++----- 4 files changed, 78 insertions(+), 57 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index b6c20e2a4d0d9..11f8d5682dfd8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -23,8 +23,6 @@ namespace Microsoft.CodeAnalysis.FindSymbols; internal partial class FindReferencesSearchEngine { - private static readonly ObjectPool s_metadataUnifyingSymbolHashSetPool = new(() => []); - private readonly Solution _solution; private readonly IImmutableSet? _documents; private readonly ImmutableArray _finders; @@ -32,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; /// @@ -60,14 +53,23 @@ 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 ? null : s_exclusiveScheduler, + }; + public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationToken) => FindReferencesAsync([symbol], cancellationToken); @@ -112,17 +114,12 @@ private async Task PerformSearchAsync( // set of documents to search, we only bother with those. var projectsToSearch = await GetProjectsToSearchAsync(allSymbols, 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. await _progressTracker.AddItemsAsync(projectsToSearch.Length, cancellationToken).ConfigureAwait(false); // 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), - cancellationToken, + GetParallelOptions(cancellationToken), async (tuple, cancellationToken) => await ProcessProjectAsync( tuple.project, tuple.allSymbols, onReferenceFound, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); } @@ -132,6 +129,11 @@ await RoslynParallel.ForEachAsync( 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)) { @@ -153,9 +155,6 @@ await RoslynParallel.ForEachAsync( } } - 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 @@ -231,10 +230,7 @@ await finder.DetermineDocumentsToSearchAsync( _options, cancellationToken).ConfigureAwait(false); foreach (var document in foundDocuments) - { - var docSymbols = GetSymbolSet(documentToSymbols, document); - docSymbols.Add(symbol); - } + GetSymbolSet(documentToSymbols, document).Add(symbol); foundDocuments.Clear(); } @@ -242,17 +238,14 @@ await finder.DetermineDocumentsToSearchAsync( await RoslynParallel.ForEachAsync( documentToSymbols, - cancellationToken, + 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); @@ -260,7 +253,7 @@ await RoslynParallel.ForEachAsync( } static MetadataUnifyingSymbolHashSet GetSymbolSet(PooledDictionary dictionary, T key) where T : notnull - => dictionary.GetOrAdd(key, static _ => s_metadataUnifyingSymbolHashSetPool.Allocate()); + => dictionary.GetOrAdd(key, static _ => MetadataUnifyingSymbolHashSet.AllocateFromPool()); } private static PooledHashSet? TryGet(Dictionary> dictionary, T key) where T : notnull @@ -296,13 +289,13 @@ private async ValueTask ProcessDocumentAsync( await RoslynParallel.ForEachAsync( symbols, - cancellationToken, + GetParallelOptions(cancellationToken), async (symbol, cancellationToken) => { // symbolToGlobalAliases is safe to read in parallel. It is created fully before this point and is no // longer mutated. - var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(cache, globalAliases); + var state = new FindReferencesDocumentState( + cache, TryGet(symbolToGlobalAliases, symbol)); await ProcessDocumentAsync(symbol, state, onReferenceFound).ConfigureAwait(false); }).ConfigureAwait(false); @@ -349,10 +342,7 @@ 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); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index b81e9bb931358..0666734493488 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -91,8 +91,8 @@ async ValueTask PerformSearchInDocumentAsync( foreach (var symbol in symbols) { - var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(cache, globalAliases); + var state = new FindReferencesDocumentState( + cache, TryGet(symbolToGlobalAliases, symbol)); await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); } 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/Shared/Utilities/RoslynParallel.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs index e0e5860e773de..feb7b5f0637cd 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs @@ -10,59 +10,78 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities; +#pragma warning disable CA1068 // CancellationToken parameters must come last + internal static class RoslynParallel { -#pragma warning disable CA1068 // CancellationToken parameters must come last - public static async Task ForEachAsync( -#pragma warning restore CA1068 // CancellationToken parameters must come last + public static Task ForEachAsync( IEnumerable source, CancellationToken cancellationToken, Func body) { + return ForEachAsync(source, new ParallelOptions() { CancellationToken = cancellationToken }, body); + } + + 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, cancellationToken, body).ConfigureAwait(false); + await Parallel.ForEachAsync(source, parallelOptions, body).ConfigureAwait(false); #else using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var item in source) { - tasks.Add(Task.Run(async () => - { - await body(item, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); + tasks.Add(CreateWorkAsync( + parallelOptions.TaskScheduler, + async () => await body(item, cancellationToken).ConfigureAwait(false), + cancellationToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); #endif } -#pragma warning disable CA1068 // CancellationToken parameters must come last - public static async Task ForEachAsync( -#pragma warning restore CA1068 // CancellationToken parameters must come last + public static Task ForEachAsync( IAsyncEnumerable source, CancellationToken cancellationToken, Func body) { + return ForEachAsync(source, new ParallelOptions() { CancellationToken = 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, cancellationToken, body).ConfigureAwait(false); + await Parallel.ForEachAsync(source, parallelOptions, body).ConfigureAwait(false); #else using var _ = ArrayBuilder.GetInstance(out var tasks); await foreach (var item in source) { - tasks.Add(Task.Run(async () => - { - await body(item, cancellationToken).ConfigureAwait(false); - }, cancellationToken)); + 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) + => scheduler is null ? createWorkAsync() : Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, scheduler).Unwrap(); } From 7a60be04c523eb04c0c7f009d567220c7aa138b6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 21:00:32 -0700 Subject: [PATCH 0993/1047] Restore --- .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 2 +- src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 11f8d5682dfd8..1bd2f4943985b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -67,7 +67,7 @@ private ParallelOptions GetParallelOptions(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 ? null : s_exclusiveScheduler, + TaskScheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler, }; public Task FindReferencesAsync(ISymbol symbol, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs index feb7b5f0637cd..f1592cd27c170 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs @@ -83,5 +83,5 @@ public static async Task ForEachAsync( } public static Task CreateWorkAsync(TaskScheduler scheduler, Func createWorkAsync, CancellationToken cancellationToken) - => scheduler is null ? createWorkAsync() : Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, scheduler).Unwrap(); + => Task.Factory.StartNew(createWorkAsync, cancellationToken, TaskCreationOptions.None, scheduler).Unwrap(); } From b0ccdd209123b487b0c5556dd2236628ea0559c8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 28 Apr 2024 22:35:12 -0700 Subject: [PATCH 0994/1047] Cleanup --- .../Core/Portable/Shared/Utilities/RoslynParallel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs index f1592cd27c170..fd0cd177b228e 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/RoslynParallel.cs @@ -19,9 +19,12 @@ public static Task ForEachAsync( CancellationToken cancellationToken, Func body) { - return ForEachAsync(source, new ParallelOptions() { CancellationToken = cancellationToken }, 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, @@ -53,7 +56,7 @@ public static Task ForEachAsync( CancellationToken cancellationToken, Func body) { - return ForEachAsync(source, new ParallelOptions() { CancellationToken = cancellationToken }, body); + return ForEachAsync(source, GetParallelOptions(cancellationToken), body); } public static async Task ForEachAsync( From 69813f423a5c63ce440abac8b88f7787f4e1fbb0 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 26 Apr 2024 12:13:30 -0700 Subject: [PATCH 0995/1047] Fix need for redundant enter when rename suggestions are not available --- .../UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d34e9dcb33a8d..074f41dfa802a 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs @@ -186,7 +186,7 @@ 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 // Handle these keystrokes when dropdown is present - || _smartRenameViewModel.IsUsingResultPanel && this.TextSelectionLength < this.Text.Length)) // Or when panel is present and text is not yet selected + || _smartRenameViewModel.IsSuggestionsPanelExpanded && this.TextSelectionLength < this.Text.Length)) // Or when panel is present and text is not yet selected { _dropDownPopup.IsOpen = false; SelectAllText(); From 304b19ea320fab65e82169e289c410840a78da41 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 26 Apr 2024 15:27:00 -0700 Subject: [PATCH 0996/1047] scope selection and double enter behavior to just the dropdown --- .../UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs | 4 ++-- .../InlineRename/UI/SmartRename/SmartRenameViewModel.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) 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 074f41dfa802a..b1ce11fbb270d 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameUserInputComboBox.xaml.cs @@ -164,6 +164,7 @@ 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; @@ -185,8 +186,7 @@ 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 // Handle these keystrokes when dropdown is present - || _smartRenameViewModel.IsSuggestionsPanelExpanded && this.TextSelectionLength < this.Text.Length)) // Or when panel is present and text is not yet selected + && _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 146fd082e8d13..77fd8957b0c5e 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -174,8 +174,12 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) SuggestedNames.Add(name); } - // Changing the list may have changed the text in the text box. We need to restore it. - BaseViewModel.IdentifierText = textInputBackup; + if (IsUsingDropdown) + { + // Changing the list may have changed the text in the text box. We need to restore it. + // This has a side effect of selecting all the text in the text box. + BaseViewModel.IdentifierText = textInputBackup; + } return; } From 33faad6d77320979a5f18477c0d6d1a7bb5c9037 Mon Sep 17 00:00:00 2001 From: Amadeusz Wieczorek Date: Fri, 26 Apr 2024 15:35:39 -0700 Subject: [PATCH 0997/1047] despite the comment, this didn't do anything, so undoing --- .../InlineRename/UI/SmartRename/SmartRenameViewModel.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs index 77fd8957b0c5e..146fd082e8d13 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/SmartRename/SmartRenameViewModel.cs @@ -174,12 +174,8 @@ private void SessionPropertyChanged(object sender, PropertyChangedEventArgs e) SuggestedNames.Add(name); } - if (IsUsingDropdown) - { - // Changing the list may have changed the text in the text box. We need to restore it. - // This has a side effect of selecting all the text in the text box. - BaseViewModel.IdentifierText = textInputBackup; - } + // Changing the list may have changed the text in the text box. We need to restore it. + BaseViewModel.IdentifierText = textInputBackup; return; } From e929877ea5c86c64de6722ea639497869f762109 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 29 Apr 2024 12:03:18 -0500 Subject: [PATCH 0998/1047] Avoid setting hasAnyCodeStyleOption for analyzers with no CodeStyleOption2 Fixes #72162 Fixes #72876 --- .../Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs | 2 +- .../FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs | 2 +- .../AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs index 2a9338532b838..b2e6ff7d86823 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs @@ -86,7 +86,7 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: true, title, message); + => CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: false, title, message); protected AbstractFileHeaderDiagnosticAnalyzer() : base(ImmutableDictionary.Empty diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs index 690ac016a84b8..dd3b659252a5e 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 Date: Mon, 29 Apr 2024 11:41:54 -0700 Subject: [PATCH 0999/1047] Using xxHash to guard the value --- .../CSharp/Impl/PackageRegistration.pkgdef | 2 +- ...o.LanguageServices.CSharp.UnitTests.csproj | 1 + .../CSharpUnifiedSettingsTests.cs | 7 ++++-- ...alStudio.LanguageServices.UnitTests.vbproj | 1 + .../VisualBasicUnifiedSettingsTests.vb | 24 ++++++++++++------- .../UnifiedSettings/UnifiedSettingsTests.vb | 16 ++++++++++++- .../Impl/PackageRegistration.pkgdef | 2 +- 7 files changed, 39 insertions(+), 14 deletions(-) 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/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index a1822e09b908e..b42bdacc847c3 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -82,5 +82,6 @@ + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs index 4258c8a03cd5b..cd9d0faf9bdc3 100644 --- a/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs +++ b/src/VisualStudio/CSharp/Test/UnifiedSettings/CSharpUnifiedSettingsTests.cs @@ -102,7 +102,7 @@ internal override object GetOptionsDefaultValue(IOption2 option) [Fact] public async Task IntelliSensePageTests() { - var registrationFileStream = typeof(CSharpUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("Roslyn.VisualStudio.CSharp.UnitTests.csharpSettings.registration.json"); + 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 }); @@ -110,7 +110,10 @@ public async Task IntelliSensePageTests() Assert.Equal("C#", actual: categoriesTitle.ToString()); var optionPageId = registrationJsonObject.SelectToken("$.categories['textEditor.csharp.intellisense'].legacyOptionPageId"); Assert.Equal(Guids.CSharpOptionPageIntelliSenseIdString, optionPageId!.ToString()); - TestUnifiedSettingsCategory(registrationJsonObject, categoryBasePath: "textEditor.csharp.intellisense", languageName: LanguageNames.CSharp); + 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/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj index 0b5136477ddd7..15fc95f510bd7 100644 --- a/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj +++ b/src/VisualStudio/Core/Test/Microsoft.VisualStudio.LanguageServices.UnitTests.vbproj @@ -64,5 +64,6 @@ + \ No newline at end of file diff --git a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb index c371b769a3a2d..7f42c3704baf2 100644 --- a/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb +++ b/src/VisualStudio/Core/Test/UnifiedSettings/VisualBasicUnifiedSettingsTests.vb @@ -76,15 +76,21 @@ Namespace Roslyn.VisualStudio.VisualBasic.UnitTests.UnifiedSettings Public Async Function IntelliSensePageTests() As Task - Dim registrationFileStream = GetType(VisualBasicUnifiedSettingsTests).GetTypeInfo().Assembly.GetManifestResourceStream("visualBasicSettings.registration.json") - Using reader = New StreamReader(registrationFileStream) - Dim registrationFile = Await reader.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) + 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 diff --git a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb index 48abfad77a558..ecf8ba700f9bf 100644 --- a/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb +++ b/src/VisualStudio/TestUtilities2/UnifiedSettings/UnifiedSettingsTests.vb @@ -3,6 +3,9 @@ ' 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 @@ -28,7 +31,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings Return [Enum].GetValues(type).Cast(Of Object).AsArray() End Function - Protected Sub TestUnifiedSettingsCategory(registrationJsonObject As JObject, categoryBasePath As String, languageName As String) + 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). @@ -62,6 +65,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.UnifiedSettings 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) diff --git a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef index e49f42d24d23f..088ecdbfeb727 100644 --- a/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef +++ b/src/VisualStudio/VisualBasic/Impl/PackageRegistration.pkgdef @@ -195,4 +195,4 @@ [$RootKey$\SettingsManifests\{574fc912-f74f-4b4e-92c3-f695c208a2bb}] @="Microsoft.VisualStudio.LanguageServices.VisualBasic.VisualBasicPackage" "ManifestPath"="$PackageFolder$\UnifiedSettings\visualBasicSettings.registration.json" -"CacheTag"=qword:08DC1824DFE0117B +"CacheTag"=qword:5DE8496A8900B809 From 451d3f0a78f331821b59b40c801f61a024f6a881 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Mon, 29 Apr 2024 15:52:35 -0700 Subject: [PATCH 1000/1047] Update configs for 17.11 branch --- eng/Versions.props | 2 +- eng/config/PublishData.json | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 78eb11bdac83d..46a3bd50e84f1 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,7 +8,7 @@ 4 11 0 - 1 + 2 $(MajorVersion).$(MinorVersion).$(PatchVersion)