From cc7c79a5462a3a1649281ef4b903010ca8f212f0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 14 Oct 2021 17:26:21 -0700 Subject: [PATCH 01/10] Remove cancellation token from tagging context object. --- .../LineSeparatorTaggerProvider.cs | 5 ++-- .../BraceHighlightingViewTaggerProvider.cs | 13 ++++++---- ...assificationBufferTaggerProvider.Tagger.cs | 9 +++---- .../SemanticClassificationUtilities.cs | 24 +++++++++---------- ...emanticClassificationViewTaggerProvider.cs | 7 ++++-- .../AbstractDiagnosticsTaggerProvider.cs | 11 +++++---- .../ActiveStatementTaggerProvider.cs | 6 +++-- .../HighlighterViewTaggerProvider.cs | 5 ++-- .../AbstractStructureTaggerProvider.cs | 7 +++--- .../InlineHintsDataTaggerProvider.cs | 5 ++-- ...ReferenceHighlightingViewTaggerProvider.cs | 19 ++++++++------- ...actAsynchronousTaggerProvider.TagSource.cs | 4 ++-- ...ousTaggerProvider.TagSource_ProduceTags.cs | 8 +++---- .../AbstractAsynchronousTaggerProvider.cs | 19 ++++++++------- .../Core/Tagging/TaggerContext.cs | 12 ++++------ .../Core/Tagging/TaggerTextChangeBehavior.cs | 7 +++--- .../Test/Tagging/AsynchronousTaggerTests.cs | 5 ++-- .../InheritanceMarginTaggerProvider.cs | 7 +++--- 18 files changed, 92 insertions(+), 81 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs index 1e1bb25c76b7f..585f686c9f2b3 100644 --- a/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/LineSeparators/LineSeparatorTaggerProvider.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; @@ -76,9 +77,9 @@ protected override ITaggerEventSource CreateEventSource( TaggerEventSources.OnTextChanged(subjectBuffer)); } - protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) + protected override async Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var document = documentSnapshotSpan.Document; if (document == null) { diff --git a/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs index 60e3039d32c5c..8ef99a4a5b9b3 100644 --- a/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/BraceMatching/BraceHighlightingViewTaggerProvider.cs @@ -55,7 +55,8 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex TaggerEventSources.OnParseOptionChanged(subjectBuffer)); } - protected override Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) + protected override Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken) { var document = documentSnapshotSpan.Document; if (!caretPosition.HasValue || document == null) @@ -63,17 +64,19 @@ protected override Task ProduceTagsAsync(TaggerContext contex return Task.CompletedTask; } - return ProduceTagsAsync(context, document, documentSnapshotSpan.SnapshotSpan.Snapshot, caretPosition.Value); + return ProduceTagsAsync( + context, document, documentSnapshotSpan.SnapshotSpan.Snapshot, caretPosition.Value, cancellationToken); } - internal async Task ProduceTagsAsync(TaggerContext context, Document document, ITextSnapshot snapshot, int position) + internal async Task ProduceTagsAsync( + TaggerContext context, Document document, ITextSnapshot snapshot, int position, CancellationToken cancellationToken) { - using (Logger.LogBlock(FunctionId.Tagger_BraceHighlighting_TagProducer_ProduceTags, context.CancellationToken)) + using (Logger.LogBlock(FunctionId.Tagger_BraceHighlighting_TagProducer_ProduceTags, cancellationToken)) { if (position >= 0 && position <= snapshot.Length) { var (bracesLeftOfPosition, bracesRightOfPosition) = await GetAllMatchingBracesAsync( - _braceMatcherService, document, position, context.CancellationToken).ConfigureAwait(false); + _braceMatcherService, document, position, cancellationToken).ConfigureAwait(false); AddBraces(context, snapshot, bracesLeftOfPosition); AddBraces(context, snapshot, bracesRightOfPosition); diff --git a/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs index b0af340e38ec4..00d2db2548ed0 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/CopyPasteAndPrintingClassificationBufferTaggerProvider.Tagger.cs @@ -124,9 +124,9 @@ public IEnumerable> GetAllTags(NormalizedSnapshotSp if (!canReuseCache) { // Our cache is not there, or is out of date. We need to compute the up to date results. - var context = new TaggerContext(document, snapshot, cancellationToken: cancellationToken); + var context = new TaggerContext(document, snapshot); this.ThreadingContext.JoinableTaskFactory.Run( - () => ProduceTagsAsync(context, new DocumentSnapshotSpan(document, spanToTag), _owner._typeMap)); + () => ProduceTagsAsync(context, new DocumentSnapshotSpan(document, spanToTag), _owner._typeMap, cancellationToken)); cachedTaggedSpan = spanToTag; cachedTags = new TagSpanIntervalTree(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans); @@ -152,11 +152,12 @@ private void GetCachedInfo(out SnapshotSpan? cachedTaggedSpan, out TagSpanInterv } } - private static Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSpan, ClassificationTypeMap typeMap) + private static Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan documentSpan, ClassificationTypeMap typeMap, CancellationToken cancellationToken) { var classificationService = documentSpan.Document.GetLanguageService(); return classificationService != null - ? SemanticClassificationUtilities.ProduceTagsAsync(context, documentSpan, classificationService, typeMap) + ? SemanticClassificationUtilities.ProduceTagsAsync(context, documentSpan, classificationService, typeMap, cancellationToken) : Task.CompletedTask; } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs index 09c672d0afcf7..3533f6f6bd12d 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationUtilities.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -18,7 +17,6 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; @@ -33,7 +31,8 @@ public static async Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, IClassificationService classificationService, - ClassificationTypeMap typeMap) + ClassificationTypeMap typeMap, + CancellationToken cancellationToken) { var document = spanToTag.Document; if (document == null) @@ -49,11 +48,11 @@ public static async Task ProduceTagsAsync( // parsing/loading. For cross language projects, this also produces semantic classifications more quickly // as we do not have to wait on skeletons to be built. - document = document.WithFrozenPartialSemantics(context.CancellationToken); + document = document.WithFrozenPartialSemantics(cancellationToken); spanToTag = new DocumentSnapshotSpan(document, spanToTag.SnapshotSpan); var classified = await TryClassifyContainingMemberSpanAsync( - context, spanToTag, classificationService, typeMap).ConfigureAwait(false); + context, spanToTag, classificationService, typeMap, cancellationToken).ConfigureAwait(false); if (classified) { return; @@ -62,14 +61,15 @@ public static async Task ProduceTagsAsync( // We weren't able to use our specialized codepaths for semantic classifying. // Fall back to classifying the full span that was asked for. await ClassifySpansAsync( - context, spanToTag, classificationService, typeMap).ConfigureAwait(false); + context, spanToTag, classificationService, typeMap, cancellationToken).ConfigureAwait(false); } private static async Task TryClassifyContainingMemberSpanAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, IClassificationService classificationService, - ClassificationTypeMap typeMap) + ClassificationTypeMap typeMap, + CancellationToken cancellationToken) { var range = context.TextChangeRange; if (range == null) @@ -85,8 +85,6 @@ private static async Task TryClassifyContainingMemberSpanAsync( return false; } - var cancellationToken = context.CancellationToken; - var lastSemanticVersion = (VersionStamp?)context.State; if (lastSemanticVersion != null) { @@ -128,7 +126,7 @@ private static async Task TryClassifyContainingMemberSpanAsync( // re-classify only the member we're inside. await ClassifySpansAsync( - context, subSpanToTag, classificationService, typeMap).ConfigureAwait(false); + context, subSpanToTag, classificationService, typeMap, cancellationToken).ConfigureAwait(false); return true; } @@ -136,7 +134,8 @@ private static async Task ClassifySpansAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, IClassificationService classificationService, - ClassificationTypeMap typeMap) + ClassificationTypeMap typeMap, + CancellationToken cancellationToken) { try { @@ -144,7 +143,6 @@ private static async Task ClassifySpansAsync( var snapshotSpan = spanToTag.SnapshotSpan; var snapshot = snapshotSpan.Snapshot; - var cancellationToken = context.CancellationToken; using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken)) { using var _ = ArrayBuilder.GetInstance(out var classifiedSpans); @@ -162,7 +160,7 @@ await AddSemanticClassificationsAsync( context.State = version; } } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { throw ExceptionUtilities.Unreachable; } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs index 89ddcbe5f71e9..b6a2b7d150845 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SemanticClassificationViewTaggerProvider.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; @@ -90,7 +91,8 @@ protected override IEnumerable GetSpansToTag(ITextView textView, I return SpecializedCollections.SingletonEnumerable(visibleSpanOpt.Value); } - protected override Task ProduceTagsAsync(TaggerContext context) + protected override Task ProduceTagsAsync( + TaggerContext context, CancellationToken cancellationToken) { Debug.Assert(context.SpansToTag.IsSingle()); @@ -113,7 +115,8 @@ protected override Task ProduceTagsAsync(TaggerContext conte if (workspaceContextService?.IsInLspEditorContext() == true) return Task.CompletedTask; - return SemanticClassificationUtilities.ProduceTagsAsync(context, spanToTag, classificationService, _typeMap); + return SemanticClassificationUtilities.ProduceTagsAsync( + context, spanToTag, classificationService, _typeMap, cancellationToken); } } } diff --git a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs index a6e9773fcc95e..829619b272c62 100644 --- a/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Diagnostics/AbstractDiagnosticsTaggerProvider.cs @@ -118,12 +118,14 @@ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, I protected internal virtual ImmutableArray GetLocationsToTag(DiagnosticData diagnosticData) => diagnosticData.DataLocation is object ? ImmutableArray.Create(diagnosticData.DataLocation) : ImmutableArray.Empty; - protected override Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition) + protected override Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) { - return ProduceTagsAsync(context, spanToTag); + return ProduceTagsAsync(context, spanToTag, cancellationToken); } - private async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag) + private async Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan spanToTag, CancellationToken cancellationToken) { if (!this.IsEnabled) { @@ -138,7 +140,6 @@ private async Task ProduceTagsAsync(TaggerContext context, DocumentSnapsho var editorSnapshot = spanToTag.SnapshotSpan.Snapshot; - var cancellationToken = context.CancellationToken; var workspace = document.Project.Solution.Workspace; // See if we've marked any spans as those we want to suppress diagnostics for. @@ -149,7 +150,7 @@ private async Task ProduceTagsAsync(TaggerContext context, DocumentSnapsho buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans); var buckets = _diagnosticService.GetPushDiagnosticBuckets( - workspace, document.Project.Id, document.Id, InternalDiagnosticsOptions.NormalDiagnosticMode, context.CancellationToken); + workspace, document.Project.Id, document.Id, InternalDiagnosticsOptions.NormalDiagnosticMode, cancellationToken); foreach (var bucket in buckets) { diff --git a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs index 1a727222eb8ba..2a5b9f311bce2 100644 --- a/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/EditAndContinue/ActiveStatementTaggerProvider.cs @@ -6,6 +6,7 @@ using System.ComponentModel.Composition; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; @@ -55,7 +56,8 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer)); } - protected override async Task ProduceTagsAsync(TaggerContext context) + protected override async Task ProduceTagsAsync( + TaggerContext context, CancellationToken cancellationToken) { Debug.Assert(context.SpansToTag.IsSingle()); @@ -75,7 +77,7 @@ protected override async Task ProduceTagsAsync(TaggerContext con var snapshot = spanToTag.SnapshotSpan.Snapshot; - var activeStatementSpans = await activeStatementTrackingService.GetAdjustedTrackingSpansAsync(document, snapshot, context.CancellationToken).ConfigureAwait(false); + var activeStatementSpans = await activeStatementTrackingService.GetAdjustedTrackingSpansAsync(document, snapshot, cancellationToken).ConfigureAwait(false); foreach (var activeStatementSpan in activeStatementSpans) { if (activeStatementSpan.IsLeaf) diff --git a/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs index ff372be59130e..aeef3cb7e90e0 100644 --- a/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/KeywordHighlighting/HighlighterViewTaggerProvider.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; @@ -62,9 +63,9 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex TaggerEventSources.OnParseOptionChanged(subjectBuffer)); } - protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) + protected override async Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var document = documentSnapshotSpan.Document; // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/763988 diff --git a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs index 0f0fc8d096f81..439ce2722c06d 100644 --- a/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Structure/AbstractStructureTaggerProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; @@ -125,7 +126,7 @@ protected sealed override ITaggerEventSource CreateEventSource(ITextView textVie } protected sealed override async Task ProduceTagsAsync( - TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) + TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken) { try { @@ -142,13 +143,13 @@ protected sealed override async Task ProduceTagsAsync( return; var blockStructure = await outliningService.GetBlockStructureAsync( - documentSnapshotSpan.Document, context.CancellationToken).ConfigureAwait(false); + documentSnapshotSpan.Document, cancellationToken).ConfigureAwait(false); ProcessSpans( context, documentSnapshotSpan.SnapshotSpan, outliningService, blockStructure.Spans); } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { throw ExceptionUtilities.Unreachable; } diff --git a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs index b224f31503bc9..5ba487c5deba2 100644 --- a/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs +++ b/src/EditorFeatures/Core/InlineHints/InlineHintsDataTaggerProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; @@ -93,9 +94,9 @@ protected override IEnumerable GetSpansToTag(ITextView textView, I return SpecializedCollections.SingletonEnumerable(visibleSpanOpt.Value); } - protected override async Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition) + protected override async Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var document = documentSnapshotSpan.Document; if (document == null) return; diff --git a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs index 3313cbbff9531..481334c8e3a2c 100644 --- a/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs +++ b/src/EditorFeatures/Core/ReferenceHighlighting/ReferenceHighlightingViewTaggerProvider.cs @@ -10,6 +10,7 @@ using System.ComponentModel.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.Editor.Shared.Options; @@ -88,7 +89,8 @@ protected override IEnumerable GetSpansToTag(ITextView textViewOpt .ToList(); } - protected override Task ProduceTagsAsync(TaggerContext context) + protected override Task ProduceTagsAsync( + TaggerContext context, CancellationToken cancellationToken) { // NOTE(cyrusn): Normally we'd limit ourselves to producing tags in the span we were // asked about. However, we want to produce all tags here so that the user can actually @@ -130,16 +132,15 @@ protected override Task ProduceTagsAsync(TaggerContext co } // Otherwise, we need to go produce all tags. - return ProduceTagsAsync(context, caretPosition, document); + return ProduceTagsAsync(context, caretPosition, document, cancellationToken); } internal static async Task ProduceTagsAsync( TaggerContext context, SnapshotPoint position, - Document document) + Document document, + CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; - var solution = document.Project.Solution; using (Logger.LogBlock(FunctionId.Tagger_ReferenceHighlighting_TagProducer_ProduceTags, cancellationToken)) @@ -158,7 +159,7 @@ internal static async Task ProduceTagsAsync( { foreach (var documentHighlights in documentHighlightsList) { - AddTagSpans(context, documentHighlights); + AddTagSpans(context, documentHighlights, cancellationToken); } } } @@ -168,9 +169,9 @@ internal static async Task ProduceTagsAsync( private static void AddTagSpans( TaggerContext context, - DocumentHighlights documentHighlights) + DocumentHighlights documentHighlights, + CancellationToken cancellationToken) { - var cancellationToken = context.CancellationToken; var document = documentHighlights.Document; var textSnapshot = context.SpansToTag.FirstOrDefault(s => s.Document == document).SnapshotSpan.Snapshot; @@ -190,7 +191,7 @@ private static void AddTagSpans( textSnapshot.GetSpan(Span.FromBounds(span.TextSpan.Start, span.TextSpan.End)), tag)); } } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e)) + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { // report NFW and continue. // also, rather than return partial results, return nothing diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index 36086a86c84df..87da3ac683512 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -25,12 +25,12 @@ internal partial class AbstractAsynchronousTaggerProvider { /// /// The is the core part of our asynchronous - /// tagging infrastructure. It is the coordinator between s, + /// tagging infrastructure. It is the coordinator between s, /// s, and s. /// /// The is the type that actually owns the /// list of cached tags. When an says tags need to be recomputed, - /// the tag source starts the computation and calls to build + /// the tag source starts the computation and calls to build /// the new list of tags. When that's done, the tags are stored in . The /// tagger, when asked for tags from the editor, then returns the tags that are stored in /// diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 7381dad55cd5c..9723c85c686bb 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -225,8 +225,8 @@ private async Task RecomputeTagsForegroundAsync(bool initialTags, CancellationTo // Create a context to store pass the information along and collect the results. var context = new TaggerContext( - oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees, cancellationToken); - await ProduceTagsAsync(context).ConfigureAwait(false); + oldState, spansToTag, caretPosition, textChangeRange, oldTagTrees); + await ProduceTagsAsync(context, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -378,12 +378,12 @@ private bool ShouldSkipTagProduction() perLanguageOptions.Any(option => !_subjectBuffer.GetFeatureOnOffOption(option)); } - private Task ProduceTagsAsync(TaggerContext context) + private Task ProduceTagsAsync(TaggerContext context, CancellationToken cancellationToken) { // If the feature is disabled, then just produce no tags. return ShouldSkipTagProduction() ? Task.CompletedTask - : _dataSource.ProduceTagsAsync(context); + : _dataSource.ProduceTagsAsync(context, cancellationToken); } private static Dictionary ProcessNewTagTrees( diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 97becfd3a3932..6b7e35ca40f96 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -7,6 +7,7 @@ using System.Diagnostics; #endif using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; @@ -38,7 +39,7 @@ internal abstract partial class AbstractAsynchronousTaggerProvider : Foreg /// /// If this is specified the tagger engine will track text changes and pass them along as /// when calling - /// . + /// . /// protected virtual TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.None; @@ -158,7 +159,7 @@ private void StoreTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer, Ta /// Called by the infrastructure to /// determine the caret position. This value will be passed in as the value to /// in the call to - /// . + /// . /// protected virtual SnapshotPoint? GetCaretPoint(ITextView textViewOpt, ITextBuffer subjectBuffer) => textViewOpt?.GetCaretPoint(subjectBuffer); @@ -169,7 +170,7 @@ private void StoreTagSource(ITextView textViewOpt, ITextBuffer subjectBuffer, Ta /// notifications from the that something has changed, and /// will only be called from the UI thread. The tagger infrastructure will then determine /// the s associated with these s - /// and will asynchronously call into at some point in + /// and will asynchronously call into at some point in /// the future to produce tags for these spans. /// protected virtual IEnumerable GetSpansToTag(ITextView textViewOpt, ITextBuffer subjectBuffer) @@ -187,14 +188,16 @@ protected virtual IEnumerable GetSpansToTag(ITextView textViewOpt, /// /// Produce tags for the given context. /// - protected virtual async Task ProduceTagsAsync(TaggerContext context) + protected virtual async Task ProduceTagsAsync( + TaggerContext context, CancellationToken cancellationToken) { foreach (var spanToTag in context.SpansToTag) { - context.CancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); await ProduceTagsAsync( context, spanToTag, - GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan)).ConfigureAwait(false); + GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan), + cancellationToken).ConfigureAwait(false); } } @@ -204,7 +207,7 @@ await ProduceTagsAsync( ? caretPosition.Value.Position : null; } - protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition) + protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) => Task.CompletedTask; internal TestAccessor GetTestAccessor() @@ -232,7 +235,7 @@ public TestAccessor(AbstractAsynchronousTaggerProvider provider) => _provider = provider; internal Task ProduceTagsAsync(TaggerContext context) - => _provider.ProduceTagsAsync(context); + => _provider.ProduceTagsAsync(context, CancellationToken.None); } } } diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index 7a4131b4e6648..86a9fc4a3b2e2 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -34,11 +34,10 @@ internal class TaggerContext where TTag : ITag /// must be specified in . /// public TextChangeRange? TextChangeRange { get; } - public CancellationToken CancellationToken { get; } /// /// The state of the tagger. Taggers can use this to keep track of information across calls - /// to . Note: state will + /// to . Note: state will /// only be preserved if the tagger infrastructure fully updates itself with the tags that /// were produced. i.e. if that tagging pass is canceled, then the state set here will not /// be preserved and the previous preserved state will be used the next time ProduceTagsAsync @@ -50,10 +49,9 @@ internal class TaggerContext where TTag : ITag internal TaggerContext( Document document, ITextSnapshot snapshot, SnapshotPoint? caretPosition = null, - TextChangeRange? textChangeRange = null, - CancellationToken cancellationToken = default) + TextChangeRange? textChangeRange = null) : this(state: null, ImmutableArray.Create(new DocumentSnapshotSpan(document, snapshot.GetFullSpan())), - caretPosition, textChangeRange, existingTags: null, cancellationToken) + caretPosition, textChangeRange, existingTags: null) { } @@ -62,14 +60,12 @@ internal TaggerContext( ImmutableArray spansToTag, SnapshotPoint? caretPosition, TextChangeRange? textChangeRange, - ImmutableDictionary> existingTags, - CancellationToken cancellationToken) + ImmutableDictionary> existingTags) { this.State = state; this.SpansToTag = spansToTag; this.CaretPosition = caretPosition; this.TextChangeRange = textChangeRange; - this.CancellationToken = cancellationToken; _spansTagged = spansToTag; _existingTags = existingTags; diff --git a/src/EditorFeatures/Core/Tagging/TaggerTextChangeBehavior.cs b/src/EditorFeatures/Core/Tagging/TaggerTextChangeBehavior.cs index 30474f60d2801..b38d462778dc0 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerTextChangeBehavior.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerTextChangeBehavior.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; +using System.Threading; namespace Microsoft.CodeAnalysis.Editor.Tagging { @@ -23,14 +22,14 @@ internal enum TaggerTextChangeBehavior /// /// The async tagger infrastructure will track text changes to the subject buffer it is /// attached to. The text changes will be provided to the - /// that is passed to . + /// that is passed to . /// TrackTextChanges = 1 << 0, /// /// The async tagger infrastructure will track text changes to the subject buffer it is /// attached to. The text changes will be provided to the - /// that is passed to . + /// that is passed to . /// /// On any edit, tags that intersect the text change range will immediately removed. /// diff --git a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs index 95577776c4635..a692484381b96 100644 --- a/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs +++ b/src/EditorFeatures/Test/Tagging/AsynchronousTaggerTests.cs @@ -164,9 +164,10 @@ public TestTaggerProvider( protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer) => _eventSource; - protected override Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan snapshotSpan, int? caretPosition) + protected override Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan snapshotSpan, int? caretPosition, CancellationToken cancellationToken) { - var tags = _callback(snapshotSpan.SnapshotSpan, context.CancellationToken); + var tags = _callback(snapshotSpan.SnapshotSpan, cancellationToken); if (tags != null) { foreach (var tag in tags) diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs index 37c2f9448e3fc..edd2f0b92773c 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceMarginTaggerProvider.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.ComponentModel.Composition; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Implementation.Classification; @@ -77,7 +78,8 @@ protected override IEnumerable GetSpansToTag(ITextView textView, I protected override async Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, - int? caretPosition) + int? caretPosition, + CancellationToken cancellationToken) { var document = spanToTag.Document; if (document == null) @@ -85,10 +87,7 @@ protected override async Task ProduceTagsAsync( return; } - var cancellationToken = context.CancellationToken; - var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - var optionValue = optionSet.GetOption(FeatureOnOffOptions.ShowInheritanceMargin); var shouldDisableFeature = optionValue == false; From 5f6895fbceb7e9a27dcdc4e23953bcb486e8eb44 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 14 Oct 2021 17:49:44 -0700 Subject: [PATCH 02/10] Restore TS stubs --- .../Tagging/AbstractAsynchronousTaggerProvider.cs | 12 ++++++++++++ src/EditorFeatures/Core/Tagging/TaggerContext.cs | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 6b7e35ca40f96..fe647f4cce0b3 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.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; #if DEBUG using System.Diagnostics; @@ -208,6 +209,17 @@ await ProduceTagsAsync( } protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) + { +#pragma warning disable 0618 + // Keep legacy shape for TypeScript. Once they adapt to the obsoletes and move to overriding this method + // we can remove the method below and this code. + context.CancellationToken = cancellationToken; + return ProduceTagsAsync(context, spanToTag, caretPosition); +#pragma warning restore + } + + [Obsolete("Legacy API for TypeScript. Override ProduceTagsAsync that takes CancellationToken.", error: false)] + protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan snapshotSpan, int? caretPosition) => Task.CompletedTask; internal TestAccessor GetTestAccessor() diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index 86a9fc4a3b2e2..25a7081697c8d 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -45,6 +45,9 @@ internal class TaggerContext where TTag : ITag /// public object State { get; set; } + [Obsolete("This will be removed. Override ProduceTags that takes a CancellationToken and use that", error: false)] + public CancellationToken CancellationToken { get; internal set; } + // For testing only. internal TaggerContext( Document document, ITextSnapshot snapshot, From 16223421355eaa2d6508f0f806badaee3b9a33d8 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 14 Oct 2021 13:41:18 -0700 Subject: [PATCH 03/10] Ensure we don't throw when we filter an incomplete list to be empty --- .../Handlers/Completion/CompletionHandler.cs | 5 ++ .../Completion/CompletionTests.cs | 65 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs index b3f650ff9959b..5a88dbc05fd79 100644 --- a/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs +++ b/src/EditorFeatures/Core/Implementation/LanguageServer/Handlers/Completion/CompletionHandler.cs @@ -356,6 +356,11 @@ static async Task CreateCompletionItemAsync( static void PromoteCommonCommitCharactersOntoList(LSP.VSInternalCompletionList completionList) { + if (completionList.Items.IsEmpty()) + { + return; + } + var defaultCommitCharacters = CompletionRules.Default.DefaultCommitCharacters.Select(c => c.ToString()).ToArray(); var commitCharacterReferences = new Dictionary(); var mostUsedCount = 0; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index d4212b6616d11..8fe2462c07645 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -1195,6 +1195,71 @@ void M() Assert.Equal(listMaxSize, results.Items.Length); } + [Fact] + public async Task TestRequestForIncompleteListFiltersDownToEmptyAsync() + { + var markup = +@"using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Configuration; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +class A +{ + void M() + { + T{|caret:|} + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var caretLocation = locations["caret"].Single(); + await testLspServer.OpenDocumentAsync(caretLocation.Uri); + + var completionParams = CreateCompletionParams( + caretLocation, + invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, + triggerCharacter: "T", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Equal(1000, results.Items.Length); + Assert.True(results.IsIncomplete); + Assert.Equal("T", results.Items.First().Label); + + await testLspServer.InsertTextAsync(caretLocation.Uri, (caretLocation.Range.End.Line, caretLocation.Range.End.Character, "z")); + + completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSInternalCompletionInvokeKind.Typing, + triggerCharacter: "z", + triggerKind: LSP.CompletionTriggerKind.TriggerForIncompleteCompletions); + + results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.True(results.IsIncomplete); + Assert.Empty(results.Items); + } + private static Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { var clientCapabilities = new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; From 2eaed7313562ed76cc519e3d2d6912e44a60d76e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 15 Oct 2021 12:14:34 -0700 Subject: [PATCH 04/10] Remove obsoletion --- .../Core/Tagging/AbstractAsynchronousTaggerProvider.cs | 4 +++- src/EditorFeatures/Core/Tagging/TaggerContext.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index fe647f4cce0b3..169dd82bc80ea 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -218,7 +218,9 @@ protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSna #pragma warning restore } - [Obsolete("Legacy API for TypeScript. Override ProduceTagsAsync that takes CancellationToken.", error: false)] + /// + /// Remove once TypeScript finishes https://github.com/dotnet/roslyn/issues/57180. + /// protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan snapshotSpan, int? caretPosition) => Task.CompletedTask; diff --git a/src/EditorFeatures/Core/Tagging/TaggerContext.cs b/src/EditorFeatures/Core/Tagging/TaggerContext.cs index 25a7081697c8d..2540ad294b6f8 100644 --- a/src/EditorFeatures/Core/Tagging/TaggerContext.cs +++ b/src/EditorFeatures/Core/Tagging/TaggerContext.cs @@ -45,7 +45,9 @@ internal class TaggerContext where TTag : ITag /// public object State { get; set; } - [Obsolete("This will be removed. Override ProduceTags that takes a CancellationToken and use that", error: false)] + /// + /// Remove once TypeScript finishes https://github.com/dotnet/roslyn/issues/57180. + /// public CancellationToken CancellationToken { get; internal set; } // For testing only. From dd07acfef1ef200d806745875ee45d3caf9900ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 15 Oct 2021 12:22:17 -0700 Subject: [PATCH 05/10] fix --- .../Core/Tagging/AbstractAsynchronousTaggerProvider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 169dd82bc80ea..1f323a6edf105 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -210,12 +210,10 @@ await ProduceTagsAsync( protected virtual Task ProduceTagsAsync(TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) { -#pragma warning disable 0618 // Keep legacy shape for TypeScript. Once they adapt to the obsoletes and move to overriding this method - // we can remove the method below and this code. + // we can remove once TypeScript finishes https://github.com/dotnet/roslyn/issues/57180. context.CancellationToken = cancellationToken; return ProduceTagsAsync(context, spanToTag, caretPosition); -#pragma warning restore } /// From 26ea332d3ea62867487f3ac5e48f815c7b464ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Sat, 16 Oct 2021 09:58:34 -0700 Subject: [PATCH 06/10] Add TypeScript External Access API for document highlighting, signature help (#57168) --- ...TypeScriptDocumentHighlightsServiceBase.cs | 25 +++++++++++++++++++ .../VSTypeScriptSignatureHelpProviderBase.cs | 25 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentHighlightsServiceBase.cs create mode 100644 src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptSignatureHelpProviderBase.cs diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentHighlightsServiceBase.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentHighlightsServiceBase.cs new file mode 100644 index 0000000000000..476f9ae333d48 --- /dev/null +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptDocumentHighlightsServiceBase.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.DocumentHighlighting; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api +{ + /// + /// TODO: Ideally, we would export TypeScript service and delegate to an imported TypeScript service implementation. + /// However, TypeScript already exports the service so we would need to coordinate the change. + /// + internal abstract class VSTypeScriptDocumentHighlightsServiceBase : IDocumentHighlightsService + { + protected abstract Task> GetDocumentHighlightsAsync( + Document document, int position, IImmutableSet documentsToSearch, CancellationToken cancellationToken); + + Task> IDocumentHighlightsService.GetDocumentHighlightsAsync( + Document document, int position, IImmutableSet documentsToSearch, CancellationToken cancellationToken) + => GetDocumentHighlightsAsync(document, position, documentsToSearch, cancellationToken); + } +} diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptSignatureHelpProviderBase.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptSignatureHelpProviderBase.cs new file mode 100644 index 0000000000000..533deef586b65 --- /dev/null +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/Api/VSTypeScriptSignatureHelpProviderBase.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.SignatureHelp; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api +{ + /// + /// TODO: Ideally, we would export TypeScript service and delegate to an imported TypeScript service implementation. + /// However, TypeScript already exports the service so we would need to coordinate the change. + /// + internal abstract class VSTypeScriptSignatureHelpProviderBase : ISignatureHelpProvider + { + Task ISignatureHelpProvider.GetItemsAsync(Document document, int position, SignatureHelpTriggerInfo triggerInfo, CancellationToken cancellationToken) + => GetItemsAsync(document, position, triggerInfo, cancellationToken); + + public abstract bool IsTriggerCharacter(char ch); + public abstract bool IsRetriggerCharacter(char ch); + + protected abstract Task GetItemsAsync(Document document, int position, SignatureHelpTriggerInfo triggerInfo, CancellationToken cancellationToken); + } +} From c17b24b3a26b323e7953ec73486e7a7855ddb016 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sat, 16 Oct 2021 11:16:36 -0700 Subject: [PATCH 07/10] Honor suppression on tuple element in switch (#57118) --- .../FlowAnalysis/NullableWalker_Patterns.cs | 15 ++- .../Semantics/NullableReferenceTypesTests.cs | 23 ++-- .../NullableReferenceTypesVsPatterns.cs | 125 ++++++++++++++++++ 3 files changed, 146 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs index 1420465f399b0..4095c35d4d275 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -215,7 +215,7 @@ int getExtendedPropertySlot(BoundPropertySubpatternMember member, int inputSlot) protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) { // first, learn from any null tests in the patterns - int slot = node.Expression.IsSuppressed ? GetOrCreatePlaceholderSlot(node.Expression) : MakeSlot(node.Expression); + int slot = GetSlotForSwitchInputValue(node.Expression); if (slot > 0) { var originalInputType = node.Expression.Type; @@ -330,7 +330,7 @@ public PossiblyConditionalState Clone() // to evaluate the patterns. In this way we infer non-nullability of the original element's parts. // We do not extend such courtesy to nested tuple literals. var originalInputElementSlots = expression is BoundTupleExpression tuple - ? tuple.Arguments.SelectAsArray(static (a, w) => w.MakeSlot(a), this) + ? tuple.Arguments.SelectAsArray(static (a, w) => w.GetSlotForSwitchInputValue(a), this) : default; var originalInputMap = PooledDictionary.GetInstance(); originalInputMap.Add(originalInputSlot, expression); @@ -512,6 +512,7 @@ public PossiblyConditionalState Clone() break; case BoundDagNonNullTest t: var inputMaybeNull = this.StateWhenTrue[inputSlot].MayBeNull(); + if (inputSlot > 0) { MarkDependentSlotsNotNull(inputSlot, inputType, ref this.StateWhenFalse); @@ -758,7 +759,7 @@ public override BoundNode VisitUnconvertedSwitchExpression(BoundUnconvertedSwitc private void VisitSwitchExpressionCore(BoundSwitchExpression node, bool inferType) { // first, learn from any null tests in the patterns - int slot = node.Expression.IsSuppressed ? GetOrCreatePlaceholderSlot(node.Expression) : MakeSlot(node.Expression); + int slot = GetSlotForSwitchInputValue(node.Expression); if (slot > 0) { var originalInputType = node.Expression.Type; @@ -774,7 +775,8 @@ private void VisitSwitchExpressionCore(BoundSwitchExpression node, bool inferTyp var endState = UnreachableState(); if (!node.ReportedNotExhaustive && node.DefaultLabel != null && - labelStateMap.TryGetValue(node.DefaultLabel, out var defaultLabelState) && defaultLabelState.believedReachable) + labelStateMap.TryGetValue(node.DefaultLabel, out var defaultLabelState) && + defaultLabelState.believedReachable) { SetState(defaultLabelState.state); var nodes = node.DecisionDag.TopologicallySortedNodes; @@ -873,6 +875,11 @@ LocalState getStateForArm(BoundSwitchExpressionArm arm) => !arm.Pattern.HasErrors && labelStateMap.TryGetValue(arm.Label, out var labelState) ? labelState.state : UnreachableState(); } + private int GetSlotForSwitchInputValue(BoundExpression node) + { + return node.IsSuppressed ? GetOrCreatePlaceholderSlot(node) : MakeSlot(node); + } + public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { Debug.Assert(!IsConditionalState); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 3eb97dca7bad6..8bc4333f834af 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -149906,13 +149906,13 @@ public void SuppressSwitchExpressionInput() var source = @"#nullable enable public class C { - public int M0(C a) => a switch { C _ => 0 }; // ok - public int M1(C? a) => a switch { C _ => 0 }; // warns - public int M2(C? a) => a! switch { C _ => 0 }; // ok + public int M0(C a) => a switch { C _ => 0 }; + public int M1(C? a) => a switch { C _ => 0 }; // 1 + public int M2(C? a) => a! switch { C _ => 0 }; - public int M3(C a) => (1, a) switch { (_, C _) => 0 }; // ok - public int M4(C? a) => (1, a) switch { (_, C _) => 0 }; // warns - public int M5(C? a) => (1, a!) switch { (_, C _) => 0 }; // warns + public int M3(C a) => (1, a) switch { (_, C _) => 0 }; + public int M4(C? a) => (1, a) switch { (_, C _) => 0 }; // 2 + public int M5(C? a) => (1, a!) switch { (_, C _) => 0 }; public void M6(C? a, bool b) { @@ -149939,23 +149939,20 @@ public void M7(C? a, bool b) break; } - a.ToString(); // warns + a.ToString(); // 3 } } "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (4,30): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern 'null' is not covered. - // public int M1(C? a) => a switch { C _ => 0 }; // warns + // public int M1(C? a) => a switch { C _ => 0 }; // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("null").WithLocation(4, 30), // (8,35): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, null)' is not covered. - // public int M4(C? a) => (1, a) switch { (_, C _) => 0 }; // warns + // public int M4(C? a) => (1, a) switch { (_, C _) => 0 }; // 2 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, null)").WithLocation(8, 35), - // (9,36): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, null)' is not covered. - // public int M5(C? a) => (1, a!) switch { (_, C _) => 0 }; // warns - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, null)").WithLocation(9, 36), // (36,9): warning CS8602: Dereference of a possibly null reference. - // a.ToString(); // warns + // a.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(36, 9) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs index 925da44dd794d..cf9ff5cdd2726 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs @@ -2621,5 +2621,130 @@ class A { public static implicit operator C?(A a) => null; } comp.VerifyDiagnostics(); } + + [Fact, WorkItem(51904, "https://github.com/dotnet/roslyn/issues/51904")] + public void TupleSwitchWithSuppression() + { + // When an input value is suppressed, it will get a dedicated + // slot during DAG analysis, instead of re-using the slot we might + // get from the expression + + var comp = CreateCompilation(@" +#nullable enable + +public class C +{ + public string M1(C? a) + => a! switch + { + C => a.ToString() // 1 + }; + + public string M11(C? a) + => a! switch + { + null => string.Empty, + C => a.ToString() // 2 + }; + + public string M111(C? a) + => a! switch + { + null => string.Empty, + _ => a.ToString() // 3 + }; + + public string M2(C? a) + => (1, a!) switch + { + (_, C) => a.ToString() // 4 + }; + + public string M22(C? a) + => (1, a!) switch + { + (_, null) => string.Empty, + (_, C) => a.ToString() // 5 + }; + + public string M222(C? a) + => (1, a!) switch + { + (_, null) => string.Empty, + (_, _) => a.ToString() // 6 + }; + + public int M2222(C? a) + => (1, a!) switch + { + (_, null) => 0, + (_, _) => 1 + }; + + public string M3(C? a) + => (1, a)! switch // 7 + { + (_, C) => a.ToString() + }; + + public string M4(C? a) + => (1, (1, a!)) switch + { + (_, (_, C)) => a.ToString() // 8 + }; + + public string M5(C? a) + => (1, (1, a)!) switch // 9 + { + (_, (_, C)) => a.ToString() // 10 + }; + + public string M6(C? a) + => (1, (1, a))! switch // 11 + { + (_, (_, C)) => a.ToString() // 12 + }; +} +"); + + comp.VerifyDiagnostics( + // (9,18): warning CS8602: Dereference of a possibly null reference. + // C => a.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(9, 18), + // (16,18): warning CS8602: Dereference of a possibly null reference. + // C => a.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(16, 18), + // (23,18): warning CS8602: Dereference of a possibly null reference. + // _ => a.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(23, 18), + // (29,23): warning CS8602: Dereference of a possibly null reference. + // (_, C) => a.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(29, 23), + // (36,23): warning CS8602: Dereference of a possibly null reference. + // (_, C) => a.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(36, 23), + // (43,23): warning CS8602: Dereference of a possibly null reference. + // (_, _) => a.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(43, 23), + // (54,20): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, null)' is not covered. + // => (1, a)! switch // 7 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, null)").WithLocation(54, 20), + // (62,28): warning CS8602: Dereference of a possibly null reference. + // (_, (_, C)) => a.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(62, 28), + // (66,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, (_, null))' is not covered. + // => (1, (1, a)!) switch // 9 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, (_, null))").WithLocation(66, 25), + // (68,28): warning CS8602: Dereference of a possibly null reference. + // (_, (_, C)) => a.ToString() // 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(68, 28), + // (72,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(_, (_, null))' is not covered. + // => (1, (1, a))! switch // 11 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(_, (_, null))").WithLocation(72, 25), + // (74,28): warning CS8602: Dereference of a possibly null reference. + // (_, (_, C)) => a.ToString() // 12 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(74, 28) + ); + } } } From eb9d627a2c9ab6f1f5f1e470434ba694cbcd54e8 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sat, 16 Oct 2021 11:17:01 -0700 Subject: [PATCH 08/10] Fix DAG crash with removable numerical test following type test (#57173) --- .../Portable/Binder/DecisionDagBuilder.cs | 5 +- .../Semantics/PatternMatchingTests2.cs | 105 ++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 43fea85f6156e..b6b79f8e7aacd 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -629,10 +629,11 @@ private Tests MakeTestsAndBindingsForRelationalPattern( BoundRelationalPattern rel, out BoundDagTemp output) { + Debug.Assert(rel.Value.Type is not null); // check if the test is always true or always false var tests = ArrayBuilder.GetInstance(2); - output = MakeConvertToType(input, rel.Syntax, rel.Value.Type!, isExplicitTest: false, tests); - var fac = ValueSetFactory.ForType(input.Type); + output = MakeConvertToType(input, rel.Syntax, rel.Value.Type, isExplicitTest: false, tests); + var fac = ValueSetFactory.ForType(rel.Value.Type); var values = fac?.Related(rel.Relation.Operator(), rel.ConstantValue); if (values?.IsEmpty == true) { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs index 033eef3174986..6c3e75abf77e7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs @@ -3565,5 +3565,110 @@ public class Wrap CompileAndVerify(source, expectedOutput: "128"); } + + [Theory, WorkItem(57148, "https://github.com/dotnet/roslyn/issues/57148")] + [InlineData("(short)0", "True")] + [InlineData("short.MinValue", "True")] + [InlineData("short.MaxValue", "True")] + [InlineData("-1", "False")] + [InlineData("(object)null", "False")] + [InlineData("string.Empty", "False")] + public void ObviousTestAfterTypeTest(string value, string expected) + { + var source = $@" +System.Console.Write(Extenders.F({value})); + +static class Extenders +{{ + public const short MaxValue = 0x7FFF; + + public static bool F(T value) + => value switch + {{ + <= MaxValue => true, + _ => false + }}; +}}"; + CompileAndVerify(source, expectedOutput: expected).VerifyDiagnostics(); + } + + [Theory, WorkItem(57148, "https://github.com/dotnet/roslyn/issues/57148")] + [InlineData("(int)0", "1")] + [InlineData("(int)255", "1")] + [InlineData("int.MinValue", "1")] + [InlineData("int.MaxValue", "4")] + [InlineData("(short)0", "2")] + [InlineData("(short)255", "2")] + [InlineData("short.MinValue", "2")] + [InlineData("short.MaxValue", "2")] + [InlineData("(uint)0", "8")] + public void ObviousTestAfterTypeTest2(string value, string expected) + { + var source = $@" +System.Console.Write(Extenders.F({value})); + +public static class Extenders +{{ + public static int F(this T value) where T : struct + {{ + int elementSize = value switch + {{ + <= 255 => 1, + <= short.MaxValue => 2, + <= int.MaxValue => 4, + _ => 8 + }}; + + return elementSize; + }} +}}"; + var comp = CreateCompilationWithSpan(source); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: expected); + } + + [Theory, WorkItem(57148, "https://github.com/dotnet/roslyn/issues/57148")] + [InlineData("(uint)0", "0")] + [InlineData("uint.MaxValue", "0")] + [InlineData("-1", "1")] + [InlineData("(object)null", "1")] + [InlineData("string.Empty", "1")] + public void ObviousTestAfterTypeTest_UnsignedIntegerNonNegative(string value, string expected) + { + var source = $@" +System.Console.Write(M({value})); + +int M(T o) +{{ + return o switch + {{ + >= (uint)0 => 0, + _ => 1 + }}; +}}"; + CompileAndVerify(source, expectedOutput: expected).VerifyDiagnostics(); + } + + [Fact, WorkItem(57148, "https://github.com/dotnet/roslyn/issues/57148")] + public void ObviousTestAfterTypeTest_UnsignedIntegerNegative() + { + var source = @" +public class C +{ + public void M(T o) + { + _ = o switch + { + < (uint)0 => 0, + _ => 2 + }; + } +}"; + CreateCompilation(source).VerifyDiagnostics( + // (8,12): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // < (uint)0 => 0, + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "< (uint)0").WithLocation(8, 12) + ); + } } } From 16c658a7e9f62ade92dc821099049e308e153497 Mon Sep 17 00:00:00 2001 From: Sandy Armstrong Date: Mon, 18 Oct 2021 00:45:18 -0700 Subject: [PATCH 09/10] Share UnnecessaryCodeFormatDefinition with VS Mac (#57193) --- .../Microsoft.CodeAnalysis.EditorFeatures.Cocoa.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/Core.Cocoa/Microsoft.CodeAnalysis.EditorFeatures.Cocoa.csproj b/src/EditorFeatures/Core.Cocoa/Microsoft.CodeAnalysis.EditorFeatures.Cocoa.csproj index c7309d9367e9c..ef02ec45167a3 100644 --- a/src/EditorFeatures/Core.Cocoa/Microsoft.CodeAnalysis.EditorFeatures.Cocoa.csproj +++ b/src/EditorFeatures/Core.Cocoa/Microsoft.CodeAnalysis.EditorFeatures.Cocoa.csproj @@ -14,6 +14,7 @@ + From 09836c274e36460f5676ca8a2aa050d79c9fe66c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 18 Oct 2021 14:26:05 -0700 Subject: [PATCH 10/10] Record status for "newlines in interpolations" (#57155) --- docs/Language Feature Status.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index a38b894893b9d..80a4cff2a07e7 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -22,7 +22,8 @@ efforts behind them. | [Top Level statement attribute specifiers](https://github.com/dotnet/csharplang/issues/5045) | [main-attributes](https://github.com/dotnet/roslyn/tree/features/features/main-attributes) | [In Progress](https://github.com/dotnet/roslyn/issues/57047) | [chsienki](https://github.com/chsienki) | TBD | [jaredpar](https://github.com/jaredpar) | | [Primary Constructors](https://github.com/dotnet/csharplang/issues/2691) | [primary-constructors](https://github.com/dotnet/roslyn/tree/features/features/primary-constructors) | [In Progress](https://github.com/dotnet/roslyn/issues/57048) | TBD | TBD | [MadsTorgersen](https://github.com/MadsTorgersen) | | [Params Span + Stackalloc any array type](https://github.com/dotnet/csharplang/issues/1757) | [params-span](https://github.com/dotnet/roslyn/tree/features/features/params-span) | [In Progress](https://github.com/dotnet/roslyn/issues/57049) | [cston](https://github.com/cston) | TBD | [jaredpar](https://github.com/jaredpar) | - +| [Newlines in interpolations](https://github.com/dotnet/csharplang/issues/4935) | main | [In Progress](https://github.com/dotnet/roslyn/issues/57154) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), TBD | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | + # C# 10.0 | Feature | Branch | State | Developer | Reviewer | LDM Champ |