diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 003ac28af6fc3..4aff1d181fdf4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -118,7 +118,7 @@ jobs: jobName: Build_Unix_Debug testArtifactName: Transport_Artifacts_Unix_Debug configuration: Debug - vmImageName: 'ubuntu-16.04' + vmImageName: 'ubuntu-18.04' - template: eng/pipelines/test-unix-job.yml parameters: @@ -127,7 +127,7 @@ jobs: buildJobName: Build_Unix_Debug testArtifactName: Transport_Artifacts_Unix_Debug configuration: Debug - testArguments: --testCoreClr --helixQueueName Ubuntu.1604.Amd64.Open + testArguments: --testCoreClr --helixQueueName Ubuntu.1804.Amd64.Open - template: eng/pipelines/test-unix-job-single-machine.yml parameters: @@ -137,7 +137,7 @@ jobs: testArtifactName: Transport_Artifacts_Unix_Debug configuration: Debug testArguments: --testCoreClr - queueName: 'BuildPool.Ubuntu.1604.amd64.Open' + queueName: 'BuildPool.Ubuntu.1804.amd64.Open' - template: eng/pipelines/test-unix-job.yml parameters: @@ -201,7 +201,7 @@ jobs: - job: Correctness_SourceBuild pool: name: NetCorePublic-Pool - queue: BuildPool.Ubuntu.1604.amd64.Open + queue: BuildPool.Ubuntu.1804.amd64.Open timeoutInMinutes: 90 steps: - template: eng/pipelines/checkout-unix-task.yml diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs index 4f888bfd6ab21..3313cbbff9531 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs @@ -53,7 +53,7 @@ public ReferenceHighlightingViewTaggerProvider( { } - protected override TaggerDelay EventChangeDelay => TaggerDelay.Short; + protected override TaggerDelay EventChangeDelay => TaggerDelay.Medium; protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer) { diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs index 4e65651ba0120..8ace9eb3e26a0 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerConstants.cs @@ -29,19 +29,12 @@ internal static TimeSpan ComputeTimeDelay(this TaggerDelay behavior, ITextBuffer } internal static TimeSpan ComputeTimeDelay(this TaggerDelay behavior) - { - switch (behavior) + => behavior switch { - case TaggerDelay.NearImmediate: - return TimeSpan.FromMilliseconds(NearImmediateDelay); - case TaggerDelay.Short: - return TimeSpan.FromMilliseconds(ShortDelay); - case TaggerDelay.Medium: - return TimeSpan.FromMilliseconds(MediumDelay); - case TaggerDelay.OnIdle: - default: - return TimeSpan.FromMilliseconds(IdleDelay); - } - } + TaggerDelay.NearImmediate => TimeSpan.FromMilliseconds(NearImmediateDelay), + TaggerDelay.Short => TimeSpan.FromMilliseconds(ShortDelay), + TaggerDelay.Medium => TimeSpan.FromMilliseconds(MediumDelay), + _ => TimeSpan.FromMilliseconds(IdleDelay), + }; } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index d63217a358497..49c52f8c0d839 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Options; @@ -61,9 +62,14 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject /// /// Work queue that collects event notifications and kicks off the work to process them. - /// The value that is passed here tells us if this is the initial tag computation or not. /// - private readonly AsyncBatchingWorkQueue _eventWorkQueue; + private Task _eventWorkQueue = Task.CompletedTask; + + /// + /// Series of tokens used to cancel previous outstanding work when new work comes in. Also used as the lock + /// to ensure threadsafe writing of _eventWorkQueue. + /// + private readonly CancellationSeries _cancellationSeries; /// /// Work queue that collects high priority requests to call TagsChanged with. @@ -120,12 +126,7 @@ public TagSource( _dataSource = dataSource; _asyncListener = asyncListener; - _eventWorkQueue = new AsyncBatchingWorkQueue( - _dataSource.EventChangeDelay.ComputeTimeDelay(), - ProcessEventsAsync, - EqualityComparer.Default, - asyncListener, - _disposalTokenSource.Token); + _cancellationSeries = new CancellationSeries(_disposalTokenSource.Token); _highPriTagsChangedQueue = new AsyncBatchingWorkQueue( TaggerDelay.NearImmediate.ComputeTimeDelay(), @@ -153,12 +154,11 @@ public TagSource( DebugRecordInitialStackTrace(); _eventSource = CreateEventSource(); - Connect(); // Start computing the initial set of tags immediately. We want to get the UI // to a complete state as soon as possible. - _eventWorkQueue.AddWork(/*initialTags*/ true); + EnqueueWork(initialTags: true); return; @@ -199,6 +199,8 @@ private void Dispose() // Stop computing any initial tags if we've been asked for them. _disposalTokenSource.Cancel(); + _cancellationSeries.Dispose(); + _disposed = true; _dataSource.RemoveTagSource(_textViewOpt, _subjectBuffer); GC.SuppressFinalize(this); diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 4b5ace4751ced..ae44c1c4de74f 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -2,8 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -15,6 +14,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -28,10 +28,7 @@ internal partial class AbstractAsynchronousTaggerProvider { private partial class TagSource { - private void OnEventSourceChanged(object sender, TaggerEventArgs _) - => _eventWorkQueue.AddWork(/*initialTags*/ false); - - private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) + private void OnCaretPositionChanged(object? _, CaretPositionChangedEventArgs e) { this.AssertIsForeground(); @@ -66,7 +63,7 @@ private void RemoveAllTags() RaiseTagsChanged(snapshot.TextBuffer, new DiffResult(added: null, removed: new(oldTagTree.GetSpans(snapshot).Select(s => s.Span)))); } - private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e) + private void OnSubjectBufferChanged(object? _, TextContentChangedEventArgs e) { this.AssertIsForeground(); UpdateTagsForTextChange(e); @@ -166,13 +163,42 @@ private TagSpanIntervalTree GetTagTree(ITextSnapshot snapshot, ImmutableDi : new TagSpanIntervalTree(snapshot.TextBuffer, _dataSource.SpanTrackingMode); } - private async Task ProcessEventsAsync(ImmutableArray events, CancellationToken cancellationToken) + private void OnEventSourceChanged(object? _1, TaggerEventArgs _2) + { + EnqueueWork(initialTags: false); + } + + private void EnqueueWork(bool initialTags) { - // Can only have at most a single `true` and `false` value in this as we are deduping these notification values. - Contract.ThrowIfTrue(events.Length > 2); - var initialTags = events.Contains(true); - await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - await RecomputeTagsForegroundAsync(initialTags, cancellationToken).ConfigureAwait(false); + lock (_cancellationSeries) + { + try + { + // cancel the last piece of computation work and enqueue the next. This doesn't apply for the very + // first tag request we make. We don't want that to be cancellable as we want that result to be + // shown as soon as possible. + var cancellationToken = initialTags ? _disposalTokenSource.Token : _cancellationSeries.CreateNext(); + + // Continue after the preceeding task unilaterally. Note that we pass LazyCancellation so that + // we still wait for that task to complete even if cancelled before we proceed. This is necessary + // as that prior task may mutate state (even if cancelled) so we cannot proceed until we know it + // is completely done. + _eventWorkQueue = _eventWorkQueue.ContinueWithAfterDelayFromAsync( + async _ => + { + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await RecomputeTagsForegroundAsync(initialTags, cancellationToken).ConfigureAwait(false); + }, + cancellationToken, + (int)_dataSource.EventChangeDelay.ComputeTimeDelay().TotalMilliseconds, + TaskContinuationOptions.None, + TaskScheduler.Default).CompletesAsyncOperation(_asyncListener.BeginAsyncOperation(nameof(EnqueueWork))); + } + catch (ObjectDisposedException) + { + // can happen if our type was disposed and we try to get a new token from _cancellationSeries. + } + } } /// @@ -187,6 +213,8 @@ private async Task ProcessEventsAsync(ImmutableArray events, CancellationT private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationToken cancellationToken) { this.AssertIsForeground(); + if (cancellationToken.IsCancellationRequested) + return; using (Logger.LogBlock(FunctionId.Tagger_TagSource_RecomputeTags, cancellationToken)) { @@ -210,6 +238,8 @@ private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationTo oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken); await ProduceTagsAsync(context).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + // Process the result to determine what changed. var newTagTrees = ComputeNewTagTrees(oldTagTrees, context); var bufferToChanges = ProcessNewTagTrees(spansToTag, oldTagTrees, newTagTrees, cancellationToken); @@ -217,8 +247,13 @@ private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationTo // Then switch back to the UI thread to update our state and kick off the work to notify the editor. await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + // Once we assign our state, we're uncancellable. We must report the changed information + // to the editor. The only case where it's ok not to is if the tagger itself is disposed. + cancellationToken = CancellationToken.None; + this.CachedTagTrees = newTagTrees; this.State = context.State; + OnTagsChangedForBuffer(bufferToChanges, initialTags); } } @@ -229,7 +264,7 @@ private ImmutableArray GetSpansAndDocumentsToTag() // TODO: Update to tag spans from all related documents. - var snapshotToDocumentMap = new Dictionary(); + using var _ = PooledDictionary.GetInstance(out var snapshotToDocumentMap); var spansToTag = _dataSource.GetSpansToTag(_textViewOpt, _subjectBuffer); var spansAndDocumentsToTag = spansToTag.SelectAsArray(span => @@ -292,7 +327,7 @@ private ImmutableDictionary> ComputeNewTa return newTagTrees; } - private TagSpanIntervalTree ComputeNewTagTree( + private TagSpanIntervalTree? ComputeNewTagTree( ImmutableDictionary> oldTagTrees, ITextBuffer textBuffer, IEnumerable> newTags, @@ -473,7 +508,7 @@ private static DiffResult ComputeDifference( return new DiffResult(new(added), new(removed)); - static ITagSpan NextOrNull(IEnumerator> enumerator) + static ITagSpan? NextOrNull(IEnumerator> enumerator) => enumerator.MoveNext() ? enumerator.Current : null; } @@ -481,7 +516,7 @@ static ITagSpan NextOrNull(IEnumerator> enumerator) /// Returns the TagSpanIntervalTree containing the tags for the given buffer. If no tags /// exist for the buffer at all, null is returned. /// - private TagSpanIntervalTree TryGetTagIntervalTreeForBuffer(ITextBuffer buffer) + private TagSpanIntervalTree? TryGetTagIntervalTreeForBuffer(ITextBuffer buffer) { this.AssertIsForeground(); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs index 02978f6882471..0258fdfe359dd 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueCapabilities.cs @@ -37,6 +37,6 @@ internal enum EditAndContinueCapabilities /// /// Creating a new type definition. /// - NewTypeDefinition = 1 << 2 + NewTypeDefinition = 1 << 4 } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs index 02a1146aad429..d5bec15d92e9b 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs @@ -86,8 +86,15 @@ public async ValueTask StartDebuggingSessionAsync(Solution solution, IManagedEdi SpecializedCollections.EmptyEnumerable>(); var runtimeCapabilities = await debuggerService.GetCapabilitiesAsync(cancellationToken).ConfigureAwait(false); + var capabilities = ParseCapabilities(runtimeCapabilities); + // For now, runtimes aren't returning capabilities, we just fall back to a known set. + if (capabilities == EditAndContinueCapabilities.None) + { + capabilities = EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.NewTypeDefinition; + } + var newSession = new DebuggingSession(solution, debuggerService, capabilities, _compilationOutputsProvider, initialDocumentStates, _debuggingSessionTelemetry, _editSessionTelemetry); var previousSession = Interlocked.CompareExchange(ref _debuggingSession, newSession, null); Contract.ThrowIfFalse(previousSession == null, "New debugging session can't be started until the existing one has ended."); diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index 387a15328a824..83316d0df9185 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -46,7 +46,9 @@ public Factory() public CompileTimeSolutionProvider(Workspace workspace) { _workspace = workspace; - _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag); + // TODO: + //_enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag); + _enabled = false; workspace.WorkspaceChanged += (s, e) => { diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs index 9d5c0f56592f0..8b8bb0770e07b 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorFactory.cs @@ -4,7 +4,9 @@ using System; using System.ComponentModel.Composition; +using System.Linq; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.EditorConfigSettings; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; @@ -79,6 +81,13 @@ public int CreateEditorInstance(uint grfCreateDoc, pgrfCDW = 0; pbstrEditorCaption = null; + if (!_workspace.CurrentSolution.Projects.Any(p => p.Language == LanguageNames.CSharp || p.Language == LanguageNames.VisualBasic)) + { + // If there are no VB or C# projects loaded in the solution (so an editorconfig file in a C++ project) then we want their + // editorfactory to present the file instead of use showing ours + return VSConstants.VS_E_UNSUPPORTEDFORMAT; + } + // Validate inputs if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0) { diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs index 9a66932e1340b..a9de1576599eb 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.LanguageServices.EditAndContinue { [Shared] - [Export(typeof(IManagedEditAndContinueLanguageService))] + [Export(typeof(IManagedHotReloadLanguageService))] [ExportMetadata("UIContext", Guids.EncCapableProjectExistsInWorkspaceUIContextString)] internal sealed class ManagedHotReloadLanguageService : IManagedHotReloadLanguageService { diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs index 06256f7418270..4c47010ceceba 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction.cs @@ -68,7 +68,7 @@ internal virtual ImmutableArray NestedCodeActions /// /// Gets custom tags for the CodeAction. /// - public ImmutableArray CustomTags { get; protected set; } = ImmutableArray.Empty; + internal ImmutableArray CustomTags { get; set; } = ImmutableArray.Empty; /// /// Used by the CodeFixService and CodeRefactoringService to add the Provider Name as a CustomTag. @@ -373,13 +373,10 @@ internal abstract class SimpleCodeAction : CodeAction { public SimpleCodeAction( string title, - string? equivalenceKey, - IEnumerable? customTags = null) + string? equivalenceKey) { Title = title; EquivalenceKey = equivalenceKey; - - CustomTags = customTags.ToImmutableArrayOrEmpty(); } public sealed override string Title { get; } @@ -392,9 +389,8 @@ public CodeActionWithNestedActions( string title, ImmutableArray nestedActions, bool isInlinable, - CodeActionPriority priority = CodeActionPriority.Medium, - IEnumerable? customTags = null) - : base(title, ComputeEquivalenceKey(nestedActions), customTags) + CodeActionPriority priority = CodeActionPriority.Medium) + : base(title, ComputeEquivalenceKey(nestedActions)) { Debug.Assert(nestedActions.Length > 0); NestedCodeActions = nestedActions; @@ -434,9 +430,8 @@ internal class DocumentChangeAction : SimpleCodeAction public DocumentChangeAction( string title, Func> createChangedDocument, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedDocument = createChangedDocument; } @@ -452,9 +447,8 @@ internal class SolutionChangeAction : SimpleCodeAction public SolutionChangeAction( string title, Func> createChangedSolution, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedSolution = createChangedSolution; } @@ -467,9 +461,8 @@ internal class NoChangeAction : SimpleCodeAction { public NoChangeAction( string title, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { } diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt index 7271abb715d8e..68ca8f2b61011 100644 --- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt @@ -1,7 +1,5 @@ abstract Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.FixAllAsync(Microsoft.CodeAnalysis.CodeFixes.FixAllContext fixAllContext, Microsoft.CodeAnalysis.Document document, System.Collections.Immutable.ImmutableArray diagnostics) -> System.Threading.Tasks.Task const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.RecordClassName = "record class name" -> string -Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.set -> void Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.DocumentBasedFixAllProvider() -> void Microsoft.CodeAnalysis.CommandLineProject diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 5440f5d1bf570..df12fa4d8bfbc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -629,6 +629,17 @@ private async Task BuildFinalStateFromInProgressStateAsync( try { var compilationWithGenerators = state.CompilationWithGeneratedDocuments; + + // If compilationWithGenerators is the same as compilationWithoutGenerators, then it means a prior run of generators + // didn't produce any files. In that case, we'll just make compilationWithGenerators null so we avoid doing any + // transformations of it multiple times. Otherwise the transformations below and in FinalizeCompilationAsync will try + // to update both at once, which is functionally fine but just unnecessary work. This function is always allowed to return + // null for compilationWithGenerators in the end, so there's no harm there. + if (compilationWithGenerators == compilationWithoutGenerators) + { + compilationWithGenerators = null; + } + var intermediateProjects = state.IntermediateProjects; while (intermediateProjects.Length > 0) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index 8fae975486f7b..5e515eaf59705 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -312,11 +312,16 @@ public async Task TreeNotReusedIfParseOptionsChangeChangeBetweenRuns() Assert.NotSame(generatedTreeBeforeChange, generatedTreeAfterChange); } - [Fact] - public async Task ChangeToDocumentThatDoesNotImpactGeneratedDocumentReusesDeclarationTree() + [Theory, CombinatorialData] + public async Task ChangeToDocumentThatDoesNotImpactGeneratedDocumentReusesDeclarationTree(bool generatorProducesTree) { using var workspace = CreateWorkspace(); - var analyzerReference = new TestGeneratorReference(new SingleFileTestGenerator("// StaticContent")); + + // We'll use either a generator that produces a single tree, or no tree, to ensure we efficiently handle both cases + ISourceGenerator generator = generatorProducesTree ? new SingleFileTestGenerator("// StaticContent") + : new CallbackGenerator(onInit: _ => { }, onExecute: _ => { }); + + var analyzerReference = new TestGeneratorReference(generator); var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference) .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs index 888238efefd53..976f450bbb254 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CustomCodeActions.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; @@ -17,13 +15,10 @@ internal abstract class SimpleCodeAction : CodeAction { public SimpleCodeAction( string title, - string? equivalenceKey = null, - IEnumerable? customTags = null) + string? equivalenceKey = null) { Title = title; EquivalenceKey = equivalenceKey; - - CustomTags = customTags.ToImmutableArrayOrEmpty(); } public sealed override string Title { get; } @@ -37,9 +32,8 @@ internal class DocumentChangeAction : SimpleCodeAction public DocumentChangeAction( string title, Func> createChangedDocument, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedDocument = createChangedDocument; } @@ -55,9 +49,8 @@ internal class SolutionChangeAction : SimpleCodeAction public SolutionChangeAction( string title, Func> createChangedSolution, - string? equivalenceKey = null, - IEnumerable? customTags = null) - : base(title, equivalenceKey, customTags) + string? equivalenceKey = null) + : base(title, equivalenceKey) { _createChangedSolution = createChangedSolution; }