From cfbb25fbd3794f091c3086480152292ef0d22519 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 10:09:21 -0800 Subject: [PATCH 01/11] Throttle designer attribute scanning, and avoid creating compilations --- ...actDesignerAttributeIncrementalAnalyzer.cs | 45 +++++++++---------- .../DesignerAttributeHelpers.cs | 14 +++--- .../VisualStudioDesignerAttributeService.cs | 22 +++------ 3 files changed, 34 insertions(+), 47 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs index 098b15c620951..5f75ce4e0ab09 100644 --- a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs @@ -59,6 +59,9 @@ public async ValueTask ProcessSolutionAsync( if (priorityDocument != null) await ProcessProjectAsync(priorityDocument.Project, priorityDocument, callback, cancellationToken).ConfigureAwait(false); + // Wait a little after the priority document and process the rest at a lower priority. + await Task.Delay(DelayTimeSpan.Short, cancellationToken).ConfigureAwait(false); + // Process the rest of the projects in dependency order so that their data is ready when we hit the // projects that depend on them. var dependencyGraph = solution.GetProjectDependencyGraph(); @@ -79,10 +82,13 @@ private async Task ProcessProjectAsync( if (!project.SupportsCompilation) return; - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var designerCategoryType = compilation.DesignerCategoryAttributeType(); - if (designerCategoryType == null) - return; + var designerCategoryType = AsyncLazy.Create( + async cancellationToken => + { + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + return compilation.DesignerCategoryAttributeType(); + }, + cacheResult: true); await ScanForDesignerCategoryUsageAsync( project, specificDocument, callback, designerCategoryType, cancellationToken).ConfigureAwait(false); @@ -96,7 +102,7 @@ private async Task ScanForDesignerCategoryUsageAsync( Project project, Document? specificDocument, IDesignerAttributeDiscoveryService.ICallback callback, - INamedTypeSymbol designerCategoryType, + AsyncLazy designerCategoryType, CancellationToken cancellationToken) { // We need to reanalyze the project whenever it (or any of its dependencies) have @@ -126,10 +132,10 @@ private async Task> ComputeChangedDataAsyn Project project, Document? specificDocument, VersionStamp projectVersion, - INamedTypeSymbol designerCategoryType, + AsyncLazy designerCategoryType, CancellationToken cancellationToken) { - using var _1 = ArrayBuilder>.GetInstance(out var tasks); + using var _ = ArrayBuilder.GetInstance(out var results); foreach (var document in project.Documents) { // If we're only analyzing a specific document, then skip the rest. @@ -149,31 +155,20 @@ private async Task> ComputeChangedDataAsyn continue; } - tasks.Add(ComputeDesignerAttributeDataAsync(designerCategoryType, document, cancellationToken)); - } - - using var _2 = ArrayBuilder.GetInstance(tasks.Count, out var results); - - // Avoid unnecessary allocation of result array. - await Task.WhenAll((IEnumerable)tasks).ConfigureAwait(false); - - foreach (var task in tasks) - { - var dataOpt = await task.ConfigureAwait(false); - if (dataOpt == null) + var data = await ComputeDesignerAttributeDataAsync( + designerCategoryType, document, cancellationToken).ConfigureAwait(false); + if (data is null) continue; - var data = dataOpt.Value; - _documentToLastReportedInformation.TryGetValue(data.DocumentId, out var existingInfo); - if (existingInfo.category != data.Category) - results.Add(data); + if (data?.Category != existingInfo.category) + results.Add(data.Value); } - return results.ToImmutableAndClear(); + return results.ToImmutable(); } private static async Task ComputeDesignerAttributeDataAsync( - INamedTypeSymbol? designerCategoryType, Document document, CancellationToken cancellationToken) + AsyncLazy designerCategoryType, Document document, CancellationToken cancellationToken) { try { diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs index 038f14f77fc8b..fc7119be2cbf3 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs @@ -8,21 +8,17 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.DesignerAttribute { internal static class DesignerAttributeHelpers { public static async Task ComputeDesignerAttributeCategoryAsync( - INamedTypeSymbol? designerCategoryType, + AsyncLazy lazyDesignerCategoryType, Document document, CancellationToken cancellationToken) { - // simple case. If there's no DesignerCategory type in this compilation, then there's - // definitely no designable types. Just immediately bail out. - if (designerCategoryType == null) - return null; - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); @@ -33,6 +29,12 @@ internal static class DesignerAttributeHelpers if (firstClass == null) return null; + // simple case. If there's no DesignerCategory type in this compilation, then there's + // definitely no designable types. + var designerCategoryType = await lazyDesignerCategoryType.GetValueAsync(cancellationToken).ConfigureAwait(false); + if (designerCategoryType == null) + return null; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var firstClassType = (INamedTypeSymbol)semanticModel.GetRequiredDeclaredSymbol(firstClass, cancellationToken); return TryGetDesignerCategory(firstClassType, designerCategoryType, cancellationToken); diff --git a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs index f8b55276c7f50..7db5b7c83e860 100644 --- a/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs +++ b/src/VisualStudio/Core/Def/DesignerAttribute/VisualStudioDesignerAttributeService.cs @@ -73,13 +73,13 @@ public VisualStudioDesignerAttributeService( var listener = asynchronousOperationListenerProvider.GetListener(FeatureAttribute.DesignerAttributes); _workQueue = new AsyncBatchingWorkQueue( - TimeSpan.FromSeconds(1), + DelayTimeSpan.Idle, this.ProcessWorkspaceChangeAsync, listener, ThreadingContext.DisposalToken); _projectSystemNotificationQueue = new AsyncBatchingWorkQueue( - TimeSpan.FromSeconds(1), + DelayTimeSpan.Idle, this.NotifyProjectSystemAsync, listener, ThreadingContext.DisposalToken); @@ -137,19 +137,15 @@ private async ValueTask NotifyProjectSystemAsync( { cancellationToken.ThrowIfCancellationRequested(); - using var _1 = ArrayBuilder.GetInstance(out var filteredInfos); + using var _ = ArrayBuilder.GetInstance(out var filteredInfos); AddFilteredInfos(data, filteredInfos); - // Now, group all the notifications by project and update all the projects in parallel. - using var _2 = ArrayBuilder.GetInstance(out var tasks); + // Now, group all the notifications by project and update that project at once. foreach (var group in filteredInfos.GroupBy(a => a.DocumentId.ProjectId)) { cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(NotifyProjectSystemAsync(group.Key, group, cancellationToken)); + await NotifyProjectSystemAsync(group.Key, group, cancellationToken).ConfigureAwait(false); } - - // Wait until all project updates have happened before processing the next batch. - await Task.WhenAll(tasks).ConfigureAwait(false); } private static void AddFilteredInfos(ImmutableSegmentedList data, ArrayBuilder filteredData) @@ -255,17 +251,11 @@ private async Task NotifyCpsProjectSystemAsync( if (!_workspace.IsPrimaryProject(projectId)) return; - // Broadcast all the information about all the documents in parallel to CPS. - - using var _ = ArrayBuilder.GetInstance(out var tasks); - foreach (var info in data) { cancellationToken.ThrowIfCancellationRequested(); - tasks.Add(NotifyCpsProjectSystemAsync(updateService, info, cancellationToken)); + await NotifyCpsProjectSystemAsync(updateService, info, cancellationToken).ConfigureAwait(false); } - - await Task.WhenAll(tasks).ConfigureAwait(false); } private static async Task NotifyCpsProjectSystemAsync( From 921d7f086b1af2f9fc6f2584f80b4a4921f835aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 10:12:31 -0800 Subject: [PATCH 02/11] Simplify --- .../DesignerAttributeHelpers.cs | 29 +++++++++---------- .../DesignerAttributeServiceTests.cs | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs index fc7119be2cbf3..85c9cb09caf1f 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs @@ -51,8 +51,20 @@ internal static class DesignerAttributeHelpers // if it has designer attribute, set it var attribute = type.GetAttributes().FirstOrDefault(d => designerCategoryType.Equals(d.AttributeClass)); - if (attribute?.ConstructorArguments.Length == 1) - return GetArgumentString(attribute.ConstructorArguments[0]); + if (attribute is + { + ConstructorArguments: + [ + { + Type.SpecialType: SpecialType.System_String, + IsNull: false, + Value: string stringValue, + } + ] + }) + { + return stringValue.Trim(); + } } return null; @@ -79,18 +91,5 @@ internal static class DesignerAttributeHelpers return null; } - - private static string? GetArgumentString(TypedConstant argument) - { - if (argument.Type == null || - argument.Type.SpecialType != SpecialType.System_String || - argument.IsNull || - argument.Value is not string stringValue) - { - return null; - } - - return stringValue.Trim(); - } } } diff --git a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs index a10722fc7775c..ea65b2d84e012 100644 --- a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs +++ b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs @@ -90,7 +90,7 @@ private static async Task TestAsync(string codeWithMarker, string category) var compilation = await document.Project.GetCompilationAsync(); var actual = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync( - compilation.DesignerCategoryAttributeType(), document, CancellationToken.None); + AsyncLazy.Create(compilation.DesignerCategoryAttributeType()), document, CancellationToken.None); Assert.Equal(category, actual); } From f714530d00ec4de72e82c909e3e6c8424c6042d9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 10:20:43 -0800 Subject: [PATCH 03/11] Simplify --- .../Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs index 85c9cb09caf1f..122666b756d07 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs @@ -57,7 +57,6 @@ internal static class DesignerAttributeHelpers [ { Type.SpecialType: SpecialType.System_String, - IsNull: false, Value: string stringValue, } ] From 482ae85ee027f5cec1d770bc45df806eff448faf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 10:23:11 -0800 Subject: [PATCH 04/11] Simplify --- ...actDesignerAttributeIncrementalAnalyzer.cs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs index 5f75ce4e0ab09..aad1d1ea5dbc2 100644 --- a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs @@ -60,7 +60,7 @@ public async ValueTask ProcessSolutionAsync( await ProcessProjectAsync(priorityDocument.Project, priorityDocument, callback, cancellationToken).ConfigureAwait(false); // Wait a little after the priority document and process the rest at a lower priority. - await Task.Delay(DelayTimeSpan.Short, cancellationToken).ConfigureAwait(false); + await Task.Delay(DelayTimeSpan.Short, cancellationToken).ConfigureAwait(false); // Process the rest of the projects in dependency order so that their data is ready when we hit the // projects that depend on them. @@ -155,22 +155,14 @@ private async Task> ComputeChangedDataAsyn continue; } - var data = await ComputeDesignerAttributeDataAsync( - designerCategoryType, document, cancellationToken).ConfigureAwait(false); - if (data is null) - continue; - - if (data?.Category != existingInfo.category) - results.Add(data.Value); + var data = await ComputeDesignerAttributeDataAsync(document).ConfigureAwait(false); + if (data.Category != existingInfo.category) + results.Add(data); } return results.ToImmutable(); - } - private static async Task ComputeDesignerAttributeDataAsync( - AsyncLazy designerCategoryType, Document document, CancellationToken cancellationToken) - { - try + async Task ComputeDesignerAttributeDataAsync(Document document) { Contract.ThrowIfNull(document.FilePath); @@ -187,10 +179,6 @@ private async Task> ComputeChangedDataAsyn FilePath = document.FilePath, }; } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) - { - return null; - } } } } From 647cef8d9351cb6c5a66fb76e3cb2ec733b93393 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 14:59:16 -0800 Subject: [PATCH 05/11] Simplify and delay more work --- ...actDesignerAttributeIncrementalAnalyzer.cs | 49 ++++++++++--------- .../AbstractDocumentHighlightsService.cs | 4 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs index aad1d1ea5dbc2..0abb26e65f7cd 100644 --- a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs @@ -4,14 +4,11 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -82,60 +79,65 @@ private async Task ProcessProjectAsync( if (!project.SupportsCompilation) return; - var designerCategoryType = AsyncLazy.Create( + // Defer expensive work until it's actually needed. + var lazyProjectVersion = AsyncLazy.Create(project.GetSemanticVersionAsync, cacheResult: true); + var lazyDesignerCategoryType = AsyncLazy.Create( async cancellationToken => { - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var firstDocument = project.Documents.FirstOrDefault(); + if (firstDocument is null) + return null; + + // we don't want to run source generators just to find out if the project has the designer category + // attribute. So freeze the project and try to get the type off of that. + var frozenProject = firstDocument.WithFrozenPartialSemantics(cancellationToken).Project; + + var compilation = await frozenProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); return compilation.DesignerCategoryAttributeType(); }, cacheResult: true); await ScanForDesignerCategoryUsageAsync( - project, specificDocument, callback, designerCategoryType, cancellationToken).ConfigureAwait(false); + project, specificDocument, callback, lazyProjectVersion, lazyDesignerCategoryType, cancellationToken).ConfigureAwait(false); // If we scanned just a specific document in the project, now scan the rest of the files. if (specificDocument != null) - await ScanForDesignerCategoryUsageAsync(project, specificDocument: null, callback, designerCategoryType, cancellationToken).ConfigureAwait(false); + await ScanForDesignerCategoryUsageAsync(project, specificDocument: null, callback, lazyProjectVersion, lazyDesignerCategoryType, cancellationToken).ConfigureAwait(false); } private async Task ScanForDesignerCategoryUsageAsync( Project project, Document? specificDocument, IDesignerAttributeDiscoveryService.ICallback callback, - AsyncLazy designerCategoryType, + AsyncLazy lazyProjectVersion, + AsyncLazy lazyDesignerCategoryType, CancellationToken cancellationToken) { - // We need to reanalyze the project whenever it (or any of its dependencies) have - // changed. We need to know about dependencies since if a downstream project adds the - // DesignerCategory attribute to a class, that can affect us when we examine the classes - // in this project. - var projectVersion = await project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - // Now get all the values that actually changed and notify VS about them. We don't need // to tell it about the ones that didn't change since that will have no effect on the // user experience. var changedData = await ComputeChangedDataAsync( - project, specificDocument, projectVersion, designerCategoryType, cancellationToken).ConfigureAwait(false); + project, specificDocument, lazyProjectVersion, lazyDesignerCategoryType, cancellationToken).ConfigureAwait(false); // Only bother reporting non-empty information to save an unnecessary RPC. if (!changedData.IsEmpty) - await callback.ReportDesignerAttributeDataAsync(changedData, cancellationToken).ConfigureAwait(false); + await callback.ReportDesignerAttributeDataAsync(changedData.SelectAsArray(d => d.data), cancellationToken).ConfigureAwait(false); // Now, keep track of what we've reported to the host so we won't report unchanged files in the future. We // do this after the report has gone through as we want to make sure that if it cancels for any reason we // don't hold onto values that may not have made it all the way to the project system. - foreach (var data in changedData) + foreach (var (data, projectVersion) in changedData) _documentToLastReportedInformation[data.DocumentId] = (data.Category, projectVersion); } - private async Task> ComputeChangedDataAsync( + private async Task> ComputeChangedDataAsync( Project project, Document? specificDocument, - VersionStamp projectVersion, - AsyncLazy designerCategoryType, + AsyncLazy lazyProjectVersion, + AsyncLazy lazyDesignerCategoryType, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var results); + using var _ = ArrayBuilder<(DesignerAttributeData data, VersionStamp version)>.GetInstance(out var results); foreach (var document in project.Documents) { // If we're only analyzing a specific document, then skip the rest. @@ -149,6 +151,7 @@ private async Task> ComputeChangedDataAsyn // If nothing has changed at the top level between the last time we analyzed this document and now, then // no need to analyze again. + var projectVersion = await lazyProjectVersion.GetValueAsync(cancellationToken).ConfigureAwait(false); if (_documentToLastReportedInformation.TryGetValue(document.Id, out var existingInfo) && existingInfo.projectVersion == projectVersion) { @@ -157,7 +160,7 @@ private async Task> ComputeChangedDataAsyn var data = await ComputeDesignerAttributeDataAsync(document).ConfigureAwait(false); if (data.Category != existingInfo.category) - results.Add(data); + results.Add((data, projectVersion)); } return results.ToImmutable(); @@ -170,7 +173,7 @@ async Task ComputeDesignerAttributeDataAsync(Document doc // So recompute here. Figure out what the current category is, and if that's different // from what we previously stored. var category = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync( - designerCategoryType, document, cancellationToken).ConfigureAwait(false); + lazyDesignerCategoryType, document, cancellationToken).ConfigureAwait(false); return new DesignerAttributeData { diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index 0d538a486036e..ec788ed67068c 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -117,7 +117,9 @@ private async Task> GetTagsForReferencedSymbo { var progress = new StreamingProgressCollector(); - var options = FindReferencesSearchOptions.GetFeatureOptionsForStartingSymbol(symbol); + // We're running in the background. So set us as 'Explicit = false' to avoid running in parallel and + // using too many resources. + var options = FindReferencesSearchOptions.GetFeatureOptionsForStartingSymbol(symbol) with { Explicit = false }; await SymbolFinder.FindReferencesInDocumentsInCurrentProcessAsync( symbol, document.Project.Solution, progress, documentsToSearch, options, cancellationToken).ConfigureAwait(false); From cff13139974981289435bd3157240f1f8b6de931 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 15:25:11 -0800 Subject: [PATCH 06/11] Simplify --- ...actDesignerAttributeIncrementalAnalyzer.cs | 47 ++++++++++--------- .../DesignerAttributeHelpers.cs | 43 +++++++---------- .../DesignerAttributeServiceTests.cs | 8 ++-- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs index 0abb26e65f7cd..b70ada293b638 100644 --- a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs @@ -4,9 +4,10 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; @@ -19,10 +20,17 @@ namespace Microsoft.CodeAnalysis.DesignerAttribute [ExportWorkspaceService(typeof(IDesignerAttributeDiscoveryService)), Shared] internal sealed partial class DesignerAttributeDiscoveryService : IDesignerAttributeDiscoveryService { + /// + /// Cache from the set of references a project has to a boolean specifying if that project knows about the + /// System.ComponentModel.DesignerCategoryAttribute attribute. Keyed by the metadata-references for a project + /// so that we don't have to recompute it in the common case where a project's references are not changing. + /// + private static readonly ConditionalWeakTable, AsyncLazy> s_metadataReferencesToDesignerAttributeInfo = new(); + /// /// Protects mutable state in this type. /// - private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); + private readonly SemaphoreSlim _gate = new(initialCount: 1); /// /// Keep track of the last information we reported. We will avoid notifying the host if we recompute and these @@ -81,28 +89,21 @@ private async Task ProcessProjectAsync( // Defer expensive work until it's actually needed. var lazyProjectVersion = AsyncLazy.Create(project.GetSemanticVersionAsync, cacheResult: true); - var lazyDesignerCategoryType = AsyncLazy.Create( - async cancellationToken => - { - var firstDocument = project.Documents.FirstOrDefault(); - if (firstDocument is null) - return null; - - // we don't want to run source generators just to find out if the project has the designer category - // attribute. So freeze the project and try to get the type off of that. - var frozenProject = firstDocument.WithFrozenPartialSemantics(cancellationToken).Project; - - var compilation = await frozenProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - return compilation.DesignerCategoryAttributeType(); - }, - cacheResult: true); + var lazyHasDesignerCategoryType = s_metadataReferencesToDesignerAttributeInfo.GetValue( + project.MetadataReferences, + _ => AsyncLazy.Create( + async cancellationToken => + { + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + return compilation.DesignerCategoryAttributeType() != null; + }, cacheResult: true)); await ScanForDesignerCategoryUsageAsync( - project, specificDocument, callback, lazyProjectVersion, lazyDesignerCategoryType, cancellationToken).ConfigureAwait(false); + project, specificDocument, callback, lazyProjectVersion, lazyHasDesignerCategoryType, cancellationToken).ConfigureAwait(false); // If we scanned just a specific document in the project, now scan the rest of the files. if (specificDocument != null) - await ScanForDesignerCategoryUsageAsync(project, specificDocument: null, callback, lazyProjectVersion, lazyDesignerCategoryType, cancellationToken).ConfigureAwait(false); + await ScanForDesignerCategoryUsageAsync(project, specificDocument: null, callback, lazyProjectVersion, lazyHasDesignerCategoryType, cancellationToken).ConfigureAwait(false); } private async Task ScanForDesignerCategoryUsageAsync( @@ -110,14 +111,14 @@ private async Task ScanForDesignerCategoryUsageAsync( Document? specificDocument, IDesignerAttributeDiscoveryService.ICallback callback, AsyncLazy lazyProjectVersion, - AsyncLazy lazyDesignerCategoryType, + AsyncLazy lazyHasDesignerCategoryType, CancellationToken cancellationToken) { // Now get all the values that actually changed and notify VS about them. We don't need // to tell it about the ones that didn't change since that will have no effect on the // user experience. var changedData = await ComputeChangedDataAsync( - project, specificDocument, lazyProjectVersion, lazyDesignerCategoryType, cancellationToken).ConfigureAwait(false); + project, specificDocument, lazyProjectVersion, lazyHasDesignerCategoryType, cancellationToken).ConfigureAwait(false); // Only bother reporting non-empty information to save an unnecessary RPC. if (!changedData.IsEmpty) @@ -134,7 +135,7 @@ private async Task ScanForDesignerCategoryUsageAsync( Project project, Document? specificDocument, AsyncLazy lazyProjectVersion, - AsyncLazy lazyDesignerCategoryType, + AsyncLazy lazyHasDesignerCategoryType, CancellationToken cancellationToken) { using var _ = ArrayBuilder<(DesignerAttributeData data, VersionStamp version)>.GetInstance(out var results); @@ -173,7 +174,7 @@ async Task ComputeDesignerAttributeDataAsync(Document doc // So recompute here. Figure out what the current category is, and if that's different // from what we previously stored. var category = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync( - lazyDesignerCategoryType, document, cancellationToken).ConfigureAwait(false); + lazyHasDesignerCategoryType, document, cancellationToken).ConfigureAwait(false); return new DesignerAttributeData { diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs index 122666b756d07..76d5e5ddf72b5 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.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.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,7 +16,7 @@ namespace Microsoft.CodeAnalysis.DesignerAttribute internal static class DesignerAttributeHelpers { public static async Task ComputeDesignerAttributeCategoryAsync( - AsyncLazy lazyDesignerCategoryType, + AsyncLazy lazyHasDesignerCategoryType, Document document, CancellationToken cancellationToken) { @@ -31,42 +32,34 @@ internal static class DesignerAttributeHelpers // simple case. If there's no DesignerCategory type in this compilation, then there's // definitely no designable types. - var designerCategoryType = await lazyDesignerCategoryType.GetValueAsync(cancellationToken).ConfigureAwait(false); - if (designerCategoryType == null) + var hasDesignerCategoryType = await lazyHasDesignerCategoryType.GetValueAsync(cancellationToken).ConfigureAwait(false); + if (!hasDesignerCategoryType) return null; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var firstClassType = (INamedTypeSymbol)semanticModel.GetRequiredDeclaredSymbol(firstClass, cancellationToken); - return TryGetDesignerCategory(firstClassType, designerCategoryType, cancellationToken); - } - private static string? TryGetDesignerCategory( - INamedTypeSymbol classType, - INamedTypeSymbol designerCategoryType, - CancellationToken cancellationToken) - { - foreach (var type in classType.GetBaseTypesAndThis()) + foreach (var type in firstClassType.GetBaseTypesAndThis()) { cancellationToken.ThrowIfCancellationRequested(); - // if it has designer attribute, set it - var attribute = type.GetAttributes().FirstOrDefault(d => designerCategoryType.Equals(d.AttributeClass)); - if (attribute is - { - ConstructorArguments: - [ - { - Type.SpecialType: SpecialType.System_String, - Value: string stringValue, - } - ] - }) - { + // See if it has the designer attribute on it. Use symbol-equivalence instead of direct equality + // as the symbol we have + var attribute = type.GetAttributes().FirstOrDefault(d => IsDesignerAttribute(d.AttributeClass)); + if (attribute is { ConstructorArguments: [{ Type.SpecialType: SpecialType.System_String, Value: string stringValue }] }) return stringValue.Trim(); - } } return null; + + static bool IsDesignerAttribute(INamedTypeSymbol? attributeClass) + => attributeClass is + { + Name: nameof(DesignerCategoryAttribute), + ContainingNamespace.Name: nameof(System.ComponentModel), + ContainingNamespace.ContainingNamespace.Name: nameof(System), + ContainingNamespace.ContainingNamespace.ContainingNamespace.IsGlobalNamespace: true, + }; } private static SyntaxNode? FindFirstNonNestedClass( diff --git a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs index ea65b2d84e012..b7166f262af71 100644 --- a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs +++ b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -86,11 +84,11 @@ private static async Task TestAsync(string codeWithMarker, string category) var hostDocument = workspace.Documents.First(); var documentId = hostDocument.Id; - var document = workspace.CurrentSolution.GetDocument(documentId); + var document = workspace.CurrentSolution.GetRequiredDocument(documentId); - var compilation = await document.Project.GetCompilationAsync(); + var compilation = await document.Project.GetRequiredCompilationAsync(CancellationToken.None); var actual = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync( - AsyncLazy.Create(compilation.DesignerCategoryAttributeType()), document, CancellationToken.None); + AsyncLazy.Create(compilation.DesignerCategoryAttributeType() != null), document, CancellationToken.None); Assert.Equal(category, actual); } From a9cbbf9fe8466c77c344cde93365b1c7e1f562e9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 15:25:41 -0800 Subject: [PATCH 07/11] NRT --- .../Test/DesignerAttribute/DesignerAttributeServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs index b7166f262af71..fe3bf0c710c5e 100644 --- a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs +++ b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs @@ -78,7 +78,7 @@ await TestAsync( class Test { }", "Form"); } - private static async Task TestAsync(string codeWithMarker, string category) + private static async Task TestAsync(string codeWithMarker, string? category) { using var workspace = TestWorkspace.CreateCSharp(codeWithMarker, openDocuments: false); From bae9787e2b03589f6ed5a52e2f7cb91044deb44d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 15:33:32 -0800 Subject: [PATCH 08/11] Simplify --- .../DesignerAttributeHelpers.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs index 76d5e5ddf72b5..7c8d9e045e923 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs @@ -25,8 +25,7 @@ internal static class DesignerAttributeHelpers // Legacy behavior. We only register the designer info for the first non-nested class // in the file. - var firstClass = FindFirstNonNestedClass( - syntaxFacts, syntaxFacts.GetMembersOfCompilationUnit(root), cancellationToken); + var firstClass = FindFirstNonNestedClass(syntaxFacts.GetMembersOfCompilationUnit(root)); if (firstClass == null) return null; @@ -60,28 +59,26 @@ static bool IsDesignerAttribute(INamedTypeSymbol? attributeClass) ContainingNamespace.ContainingNamespace.Name: nameof(System), ContainingNamespace.ContainingNamespace.ContainingNamespace.IsGlobalNamespace: true, }; - } - private static SyntaxNode? FindFirstNonNestedClass( - ISyntaxFactsService syntaxFacts, SyntaxList members, CancellationToken cancellationToken) - { - foreach (var member in members) + SyntaxNode? FindFirstNonNestedClass(SyntaxList members) { - cancellationToken.ThrowIfCancellationRequested(); - if (syntaxFacts.IsBaseNamespaceDeclaration(member)) - { - var firstClass = FindFirstNonNestedClass( - syntaxFacts, syntaxFacts.GetMembersOfBaseNamespaceDeclaration(member), cancellationToken); - if (firstClass != null) - return firstClass; - } - else if (syntaxFacts.IsClassDeclaration(member)) + foreach (var member in members) { - return member; + cancellationToken.ThrowIfCancellationRequested(); + if (syntaxFacts.IsBaseNamespaceDeclaration(member)) + { + var firstClass = FindFirstNonNestedClass(syntaxFacts.GetMembersOfBaseNamespaceDeclaration(member)); + if (firstClass != null) + return firstClass; + } + else if (syntaxFacts.IsClassDeclaration(member)) + { + return member; + } } - } - return null; + return null; + } } } } From ec2166942e4d2359a4d11e9c813da1476ee0082d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 15:36:21 -0800 Subject: [PATCH 09/11] Inline helper --- ...s => DesignerAttributeDiscoveryService.cs} | 71 +++++++++++++++- .../DesignerAttributeHelpers.cs | 84 ------------------- .../DesignerAttributeServiceTests.cs | 2 +- 3 files changed, 71 insertions(+), 86 deletions(-) rename src/Features/Core/Portable/DesignerAttribute/{AbstractDesignerAttributeIncrementalAnalyzer.cs => DesignerAttributeDiscoveryService.cs} (74%) delete mode 100644 src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs diff --git a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs similarity index 74% rename from src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs rename to src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs index b70ada293b638..57ad91f8044ef 100644 --- a/src/Features/Core/Portable/DesignerAttribute/AbstractDesignerAttributeIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs @@ -6,11 +6,14 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Composition; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -173,7 +176,7 @@ async Task ComputeDesignerAttributeDataAsync(Document doc // We either haven't computed the designer info, or our data was out of date. We need // So recompute here. Figure out what the current category is, and if that's different // from what we previously stored. - var category = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync( + var category = await ComputeDesignerAttributeCategoryAsync( lazyHasDesignerCategoryType, document, cancellationToken).ConfigureAwait(false); return new DesignerAttributeData @@ -184,5 +187,71 @@ async Task ComputeDesignerAttributeDataAsync(Document doc }; } } + + public static async Task ComputeDesignerAttributeCategoryAsync( + AsyncLazy lazyHasDesignerCategoryType, + Document document, + CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + + // Legacy behavior. We only register the designer info for the first non-nested class + // in the file. + var firstClass = FindFirstNonNestedClass(syntaxFacts.GetMembersOfCompilationUnit(root)); + if (firstClass == null) + return null; + + // simple case. If there's no DesignerCategory type in this compilation, then there's + // definitely no designable types. + var hasDesignerCategoryType = await lazyHasDesignerCategoryType.GetValueAsync(cancellationToken).ConfigureAwait(false); + if (!hasDesignerCategoryType) + return null; + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var firstClassType = (INamedTypeSymbol)semanticModel.GetRequiredDeclaredSymbol(firstClass, cancellationToken); + + foreach (var type in firstClassType.GetBaseTypesAndThis()) + { + cancellationToken.ThrowIfCancellationRequested(); + + // See if it has the designer attribute on it. Use symbol-equivalence instead of direct equality + // as the symbol we have + var attribute = type.GetAttributes().FirstOrDefault(d => IsDesignerAttribute(d.AttributeClass)); + if (attribute is { ConstructorArguments: [{ Type.SpecialType: SpecialType.System_String, Value: string stringValue }] }) + return stringValue.Trim(); + } + + return null; + + static bool IsDesignerAttribute(INamedTypeSymbol? attributeClass) + => attributeClass is + { + Name: nameof(DesignerCategoryAttribute), + ContainingNamespace.Name: nameof(System.ComponentModel), + ContainingNamespace.ContainingNamespace.Name: nameof(System), + ContainingNamespace.ContainingNamespace.ContainingNamespace.IsGlobalNamespace: true, + }; + + SyntaxNode? FindFirstNonNestedClass(SyntaxList members) + { + foreach (var member in members) + { + cancellationToken.ThrowIfCancellationRequested(); + if (syntaxFacts.IsBaseNamespaceDeclaration(member)) + { + var firstClass = FindFirstNonNestedClass(syntaxFacts.GetMembersOfBaseNamespaceDeclaration(member)); + if (firstClass != null) + return firstClass; + } + else if (syntaxFacts.IsClassDeclaration(member)) + { + return member; + } + } + + return null; + } + } } } diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs deleted file mode 100644 index 7c8d9e045e923..0000000000000 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeHelpers.cs +++ /dev/null @@ -1,84 +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.ComponentModel; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.DesignerAttribute -{ - internal static class DesignerAttributeHelpers - { - public static async Task ComputeDesignerAttributeCategoryAsync( - AsyncLazy lazyHasDesignerCategoryType, - Document document, - CancellationToken cancellationToken) - { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = document.GetRequiredLanguageService(); - - // Legacy behavior. We only register the designer info for the first non-nested class - // in the file. - var firstClass = FindFirstNonNestedClass(syntaxFacts.GetMembersOfCompilationUnit(root)); - if (firstClass == null) - return null; - - // simple case. If there's no DesignerCategory type in this compilation, then there's - // definitely no designable types. - var hasDesignerCategoryType = await lazyHasDesignerCategoryType.GetValueAsync(cancellationToken).ConfigureAwait(false); - if (!hasDesignerCategoryType) - return null; - - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var firstClassType = (INamedTypeSymbol)semanticModel.GetRequiredDeclaredSymbol(firstClass, cancellationToken); - - foreach (var type in firstClassType.GetBaseTypesAndThis()) - { - cancellationToken.ThrowIfCancellationRequested(); - - // See if it has the designer attribute on it. Use symbol-equivalence instead of direct equality - // as the symbol we have - var attribute = type.GetAttributes().FirstOrDefault(d => IsDesignerAttribute(d.AttributeClass)); - if (attribute is { ConstructorArguments: [{ Type.SpecialType: SpecialType.System_String, Value: string stringValue }] }) - return stringValue.Trim(); - } - - return null; - - static bool IsDesignerAttribute(INamedTypeSymbol? attributeClass) - => attributeClass is - { - Name: nameof(DesignerCategoryAttribute), - ContainingNamespace.Name: nameof(System.ComponentModel), - ContainingNamespace.ContainingNamespace.Name: nameof(System), - ContainingNamespace.ContainingNamespace.ContainingNamespace.IsGlobalNamespace: true, - }; - - SyntaxNode? FindFirstNonNestedClass(SyntaxList members) - { - foreach (var member in members) - { - cancellationToken.ThrowIfCancellationRequested(); - if (syntaxFacts.IsBaseNamespaceDeclaration(member)) - { - var firstClass = FindFirstNonNestedClass(syntaxFacts.GetMembersOfBaseNamespaceDeclaration(member)); - if (firstClass != null) - return firstClass; - } - else if (syntaxFacts.IsClassDeclaration(member)) - { - return member; - } - } - - return null; - } - } - } -} diff --git a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs index fe3bf0c710c5e..4d5bfcd53b415 100644 --- a/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs +++ b/src/VisualStudio/CSharp/Test/DesignerAttribute/DesignerAttributeServiceTests.cs @@ -87,7 +87,7 @@ private static async Task TestAsync(string codeWithMarker, string? category) var document = workspace.CurrentSolution.GetRequiredDocument(documentId); var compilation = await document.Project.GetRequiredCompilationAsync(CancellationToken.None); - var actual = await DesignerAttributeHelpers.ComputeDesignerAttributeCategoryAsync( + var actual = await DesignerAttributeDiscoveryService.ComputeDesignerAttributeCategoryAsync( AsyncLazy.Create(compilation.DesignerCategoryAttributeType() != null), document, CancellationToken.None); Assert.Equal(category, actual); From 8155ce3e630b73f31d3d1ed22452369e1dab3eb9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 25 Feb 2023 15:37:09 -0800 Subject: [PATCH 10/11] simplify --- .../DesignerAttribute/DesignerAttributeDiscoveryService.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs index 57ad91f8044ef..0c9998e1a91ce 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs @@ -189,9 +189,7 @@ async Task ComputeDesignerAttributeDataAsync(Document doc } public static async Task ComputeDesignerAttributeCategoryAsync( - AsyncLazy lazyHasDesignerCategoryType, - Document document, - CancellationToken cancellationToken) + AsyncLazy lazyHasDesignerCategoryType, Document document, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); From d246bcf0a62d6b1b9f023735ac13c87e307c50dd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 26 Feb 2023 01:32:41 -0800 Subject: [PATCH 11/11] Add comments --- .../DesignerAttributeDiscoveryService.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs index 0c9998e1a91ce..67acfac420460 100644 --- a/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs +++ b/src/Features/Core/Portable/DesignerAttribute/DesignerAttributeDiscoveryService.cs @@ -91,7 +91,12 @@ private async Task ProcessProjectAsync( return; // Defer expensive work until it's actually needed. + + // The top level project version for this project. We only care if anything top level changes here. + // Downstream impact will already happen due to us keying off of the references a project has (which will + // change if anything it depends on changes). var lazyProjectVersion = AsyncLazy.Create(project.GetSemanticVersionAsync, cacheResult: true); + var lazyHasDesignerCategoryType = s_metadataReferencesToDesignerAttributeInfo.GetValue( project.MetadataReferences, _ => AsyncLazy.Create( @@ -141,6 +146,12 @@ private async Task ScanForDesignerCategoryUsageAsync( AsyncLazy lazyHasDesignerCategoryType, CancellationToken cancellationToken) { + // NOTE: While we could potentially process the documents in a project in parallel, we intentionally do not. + // That's because this runs automatically in the BG in response to *any* change in the workspace. So it's + // very often going to be running, and it will be potentially competing against explicitly invoked actions + // by the user. Processing only one doc at a time, means we're not saturating the TPL with this work at the + // expense of other features. + using var _ = ArrayBuilder<(DesignerAttributeData data, VersionStamp version)>.GetInstance(out var results); foreach (var document in project.Documents) {