From adf78480a8934bd85f7fe370b4b42bb624a5415a Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Thu, 13 Feb 2020 17:23:09 -0800 Subject: [PATCH 01/11] Fix item sorting --- .../IntelliSense/AsyncCompletion/ItemManager.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs index f39cad480f0c1..648be97cdcfbd 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs @@ -67,8 +67,7 @@ public Task> SortCompletionListAsync( AsyncCompletionLogger.LogSessionWithTypeImportCompletionEnabled(); } - - return Task.FromResult(data.InitialList.OrderBy(i => i.SortText).ToImmutableArray()); + return Task.FromResult(data.InitialList.OrderBy(i => i.SortText, StringComparer.Ordinal).ToImmutableArray()); } public Task UpdateCompletionListAsync( @@ -210,7 +209,7 @@ private FilteredCompletionModel UpdateCompletionList( // Sort the items by pattern matching results. // Note that we want to preserve the original alphabetical order for items with same pattern match score, // but `ArrayBuilder.Sort` isn't stable. Therefore we have to add a monotonically increasing integer - // to `MatchResult` to archieve this. + // to `MatchResult` to achieve this. builder.Sort(MatchResult.SortingComparer); var initialListOfItemsToBeIncluded = builder.ToImmutableAndFree(); @@ -378,7 +377,7 @@ private FilteredCompletionModel HandleDeletionTrigger( { // The user has typed something, but nothing in the actual list matched what // they were typing. In this case, we want to dismiss completion entirely. - // The thought process is as follows: we aggressively brough up completion + // The thought process is as follows: we aggressively brought up completion // to help them when they typed delete (in case they wanted to pick another // item). However, they're typing something that doesn't seem to match at all // The completion list is just distracting at this point. @@ -584,7 +583,7 @@ private static bool TryCreateMatchResult( // have just typed the character to get completion. Filtering out items // here is not desirable. // - // 2. They brough up completion with ctrl-j or through deletion. In these + // 2. They brought up completion with ctrl-j or through deletion. In these // cases we just always keep all the items in the list. if (matchedFilterText || initialTriggerKind == CompletionTriggerKind.Deletion || @@ -695,7 +694,7 @@ private static bool IsHardSelection( // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. - // If all that has been typed is puntuation, then don't hard select anything. + // If all that has been typed is punctuation, then don't hard select anything. // It's possible the user is just typing language punctuation and selecting // anything in the list will interfere. We only allow this if the filter text // exactly matches something in the list already. @@ -758,7 +757,7 @@ private static bool IsAllPunctuation(string filterText) /// private static bool IsPotentialFilterCharacter(char c) { - // TODO(cyrusn): Actually use the right unicode categories here. + // TODO(cyrusn): Actually use the right Unicode categories here. return char.IsLetter(c) || char.IsNumber(c) || c == '_'; From 0a23fbaf19d6cbdc7835514c230924ecb1795e65 Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Thu, 13 Feb 2020 17:23:49 -0800 Subject: [PATCH 02/11] Add tests --- .../CSharpCompletionCommandHandlerTests.vb | 68 +++++++++++++++++++ .../TestUtilities2/Intellisense/TestState.vb | 11 +++ 2 files changed, 79 insertions(+) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 4cc5c73e775b5..ff7e48fc969e1 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -5996,6 +5996,74 @@ namespace NS End Using End Function + + + Public Async Function SortItemsByExpandedFlag() As Task + Using state = TestStateFactory.CreateCSharpTestState( + +namespace NS1 +{ + class C + { + void M() + { + $$ + } + } + + class MyTask1 { } + class MyTask2 { } + class MyTask3 { } +} + +namespace NS2 +{ + class MyTask1 { } + class MyTask2 { } + class MyTask3 { } +} +, extraExportedTypes:={GetType(TestExperimentationService)}.ToList()) + + Dim workspace = state.Workspace + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options _ + .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, False))) + + ' trigger completion with import completion disabled + state.SendInvokeCompletionList() + Await state.WaitForUIRenderedAsync() + + ' make sure expander is selected + state.SetCompletionItemExpanderState(isSelected:=True) + Await state.WaitForAsynchronousOperationsAsync() + Await state.WaitForUIRenderedAsync() + + state.SendEscape() + Await state.AssertNoCompletionSession() + + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options _ + .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True))) + + state.SendTypeChars("mytask") + Await state.WaitForAsynchronousOperationsAsync() + + ' make sure expander is selected + state.AssertCompletionItemExpander(isAvailable:=True, isSelected:=True) + Await state.AssertSelectedCompletionItem(displayText:="MyTask1", inlineDescription:="") + + + Dim expectedOrder As (String, String)() = + { + ("MyTask1", ""), + ("MyTask2", ""), + ("MyTask3", ""), + ("MyTask1", "NS2"), + ("MyTask2", "NS2"), + ("MyTask3", "NS2") + } + state.AssertItemsInOrder(expectedOrder) + End Using + End Function + Public Async Function TestSuggestedNamesDontStartWithDigit_DigitsInTheMiddle() As Task diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb index 047ab8fe2cf14..e47c12ae0b615 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb @@ -387,6 +387,17 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Next End Sub + Public Sub AssertItemsInOrder(expectedOrder As (String, String)()) + Dim session = GetExportedValue(Of IAsyncCompletionBroker)().GetSession(TextView) + Assert.NotNull(session) + Dim items = session.GetComputedItems(CancellationToken.None).Items + Assert.Equal(expectedOrder.Count, items.Count) + For i = 0 To expectedOrder.Count - 1 + Assert.Equal(expectedOrder(i).Item1, items(i).DisplayText) + Assert.Equal(expectedOrder(i).Item2, items(i).Suffix) + Next + End Sub + Public Async Function AssertSelectedCompletionItem( Optional displayText As String = Nothing, Optional displayTextSuffix As String = Nothing, From 4a2d1466aa03532a8e65c31dc0f7f257026a74e4 Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Thu, 13 Feb 2020 17:25:07 -0800 Subject: [PATCH 03/11] Upgrade VS editor packages to 16.5.173-g9c5f5bc040 This is the version used in VS 16.5 preview 2 (d16.5 16.5.29721.120) --- eng/Versions.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index ea2ef56857e5b..0200a84dab25c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -31,7 +31,7 @@ 3.3.1 1.0.1-beta1.20067.1 3.5.0-beta3-20078-04 - 16.4.248 + 16.5.173-g9c5f5bc040 5.0.0-alpha1.19409.1 16.3.27-develop-gdd55e997 @@ -106,8 +106,8 @@ 15.0.27 $(VisualStudioEditorPackagesVersion) 16.0.28226-alpha - 16.4.29315.20-pre - 16.4.29315.20-pre + 16.5.29430.44 + 16.5.29430.44 2.8.0 $(VisualStudioEditorPackagesVersion) 15.8.27812-alpha @@ -155,7 +155,7 @@ 16.0.0 16.4.16 16.4.16 - 16.4.29417.175 + 16.5.29430.44 15.5.31 2.0.0-rc3-61304-01 4.3.0 From 39f9cdbbf3be35fa80e7ee47a07dbfd55f696e7d Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Thu, 13 Feb 2020 14:22:14 -0800 Subject: [PATCH 04/11] Mark unimported attribute item as "expanded" when in attribute context --- .../Core/Portable/Completion/CompletionItemFlags.cs | 4 ++-- .../AbstractTypeImportCompletionService.CacheEntry.cs | 2 +- .../ImportCompletionProvider/ImportCompletionItem.cs | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Completion/CompletionItemFlags.cs b/src/Features/Core/Portable/Completion/CompletionItemFlags.cs index 9d931d2dfddf0..4aff82533f5b7 100644 --- a/src/Features/Core/Portable/Completion/CompletionItemFlags.cs +++ b/src/Features/Core/Portable/Completion/CompletionItemFlags.cs @@ -13,8 +13,8 @@ internal enum CompletionItemFlags /// /// Indicates this is cached and reused across completion sessions. - /// This might be used by completion system for things like deciding whether it can safaly cache and reuse - /// other data correspodning to this item. + /// This might be used by completion system for things like deciding whether it can safely cache and reuse + /// other data corresponding to this item. /// /// TODO: Revisit the approach we used for caching VS items. /// https://github.com/dotnet/roslyn/issues/35160 diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractTypeImportCompletionService.CacheEntry.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractTypeImportCompletionService.CacheEntry.cs index 692ad9be96e0f..78b703483c38e 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractTypeImportCompletionService.CacheEntry.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractTypeImportCompletionService.CacheEntry.cs @@ -87,7 +87,7 @@ static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, if (attributeItem.DisplayText.TryGetWithoutAttributeSuffix(isCaseSensitive: isCaseSensitive, out var attributeNameWithoutSuffix)) { // We don't want to cache this item. - return ImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix); + return ImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix, CompletionItemFlags.Expanded); } return attributeItem; diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index f365a7dac6ceb..e4e29a6035f82 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -68,7 +68,7 @@ public static CompletionItem Create(string name, int arity, string containingNam return item; } - public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem attributeItem, string attributeNameWithoutSuffix) + public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem attributeItem, string attributeNameWithoutSuffix, CompletionItemFlags flags) { Debug.Assert(!attributeItem.Properties.ContainsKey(AttributeFullName)); @@ -78,7 +78,7 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att var sortTextBuilder = PooledStringBuilder.GetInstance(); sortTextBuilder.Builder.AppendFormat(SortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription); - return CompletionItem.Create( + var item = CompletionItem.Create( displayText: attributeNameWithoutSuffix, sortText: sortTextBuilder.ToStringAndFree(), properties: newProperties, @@ -87,6 +87,9 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att displayTextPrefix: attributeItem.DisplayTextPrefix, displayTextSuffix: attributeItem.DisplayTextSuffix, inlineDescription: attributeItem.InlineDescription); + + item.Flags = flags; + return item; } public static CompletionItem CreateItemWithGenericDisplaySuffix(CompletionItem item, string genericTypeSuffix) From 17e52cc846a3d3df6a9a356bf1490a66327f8597 Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Fri, 14 Feb 2020 15:33:36 -0800 Subject: [PATCH 05/11] Fix sorting --- .../AsyncCompletion/ItemManager.cs | 35 ++++++++++++++++++- .../ImportCompletionItem.cs | 7 ++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs index 648be97cdcfbd..72bb215632c89 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; @@ -10,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -67,7 +69,7 @@ public Task> SortCompletionListAsync( AsyncCompletionLogger.LogSessionWithTypeImportCompletionEnabled(); } - return Task.FromResult(data.InitialList.OrderBy(i => i.SortText, StringComparer.Ordinal).ToImmutableArray()); + return Task.FromResult(data.InitialList.Sort(CompletionItemSortingComparer.Instance)); } public Task UpdateCompletionListAsync( @@ -762,5 +764,36 @@ private static bool IsPotentialFilterCharacter(char c) || char.IsNumber(c) || c == '_'; } + + /// + /// This comparer can handle expanded items. + /// + private class CompletionItemSortingComparer : IComparer + { + public static CompletionItemSortingComparer Instance { get; } = new CompletionItemSortingComparer(); + + public int Compare(VSCompletionItem x, VSCompletionItem y) + { + var xHasPrefix = HasExpandedPrefix(x); + var yHasPrefix = HasExpandedPrefix(y); + + if (xHasPrefix == yHasPrefix) + { + // Don't use Ordinal because we want lowercase to be "smaller" than uppercase. + // e.g. "Goo" before "GoTo" + return string.Compare(x.SortText, y.SortText, StringComparison.InvariantCulture); + } + else if (xHasPrefix) + { + return 1; + } + else + { + return -1; + } + + static bool HasExpandedPrefix(VSCompletionItem i) => i.SortText.Length > 1 && i.SortText[0] == ImportCompletionItem.SortTextPrefix; + } + } } } diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index e4e29a6035f82..5a8eacf352222 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -16,7 +16,8 @@ namespace Microsoft.CodeAnalysis.Completion.Providers { internal static class ImportCompletionItem { - private const string SortTextFormat = "~{0} {1}"; + public const char SortTextPrefix = '~'; + private static readonly string s_sortTextFormat = $"{SortTextPrefix}{0} {1}"; private const string TypeAritySuffixName = nameof(TypeAritySuffixName); private const string AttributeFullName = nameof(AttributeFullName); @@ -52,7 +53,7 @@ public static CompletionItem Create(string name, int arity, string containingNam // 2. ' ' before namespace makes types with identical type name but from different namespace all show up in the list, // it also makes sure type with shorter name shows first, e.g. 'SomeType` before 'SomeTypeWithLongerName'. var sortTextBuilder = PooledStringBuilder.GetInstance(); - sortTextBuilder.Builder.AppendFormat(SortTextFormat, name, containingNamespace); + sortTextBuilder.Builder.AppendFormat(s_sortTextFormat, name, containingNamespace); var item = CompletionItem.Create( displayText: name, @@ -76,7 +77,7 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att var newProperties = attributeItem.Properties.Add(AttributeFullName, attributeItem.DisplayText); var sortTextBuilder = PooledStringBuilder.GetInstance(); - sortTextBuilder.Builder.AppendFormat(SortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription); + sortTextBuilder.Builder.AppendFormat(s_sortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription); var item = CompletionItem.Create( displayText: attributeNameWithoutSuffix, From b588dde808e9d984b7871cbbb8506766e61f0b95 Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Fri, 21 Feb 2020 16:09:10 -0800 Subject: [PATCH 06/11] Fix inconsistent sorting --- .../AsyncCompletion/CompletionSource.cs | 4 +- .../AsyncCompletion/ItemManager.cs | 60 +++++++------------ .../Portable/Completion/CompletionItem.cs | 25 ++++++-- .../ImportCompletionItem.cs | 14 ++--- 4 files changed, 48 insertions(+), 55 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs index 6adf3302ac75a..1226e85bf4db2 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -108,7 +108,7 @@ public AsyncCompletionData.CompletionStartData InitializeCompletion( // Therefore, in each completion session we use a list of commit character for a specific completion service and a specific content type. _textView.Properties[PotentialCommitCharacters] = service.GetRules().DefaultCommitCharacters; - // Reset a flag which means a snippet triggerred by ? + Tab. + // Reset a flag which means a snippet triggered by ? + Tab. // Set it later if met the condition. _snippetCompletionTriggeredIndirectly = false; @@ -373,7 +373,7 @@ public async Task GetDescriptionAsync(IAsyncCompletionSession session, V } /// - /// We'd like to cache VS Completion item dircetly to avoid allocation completely. However it holds references + /// We'd like to cache VS Completion item directly to avoid allocation completely. However it holds references /// to transient objects, which would cause memory leak (among other potential issues) if cached. /// So as a compromise, we cache data that can be calculated from Roslyn completion item to avoid repeated /// calculation cost for cached Roslyn completion items. diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs index 72bb215632c89..862daf4144f11 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/ItemManager.cs @@ -69,7 +69,9 @@ public Task> SortCompletionListAsync( AsyncCompletionLogger.LogSessionWithTypeImportCompletionEnabled(); } - return Task.FromResult(data.InitialList.Sort(CompletionItemSortingComparer.Instance)); + // Sort by default comparer of Roslyn CompletionItem + var sortedItems = data.InitialList.OrderBy(GetOrAddRoslynCompletionItem).ToImmutableArray(); + return Task.FromResult(sortedItems); } public Task UpdateCompletionListAsync( @@ -540,6 +542,22 @@ private static int GetRecentItemIndex(ImmutableArray recentItems, Roslyn return -index; } + private static RoslynCompletionItem GetOrAddRoslynCompletionItem(VSCompletionItem vsItem) + { + if (!vsItem.Properties.TryGetProperty(CompletionSource.RoslynItem, out RoslynCompletionItem roslynItem)) + { + roslynItem = RoslynCompletionItem.Create( + displayText: vsItem.DisplayText, + filterText: vsItem.FilterText, + sortText: vsItem.SortText, + displayTextSuffix: vsItem.Suffix); + + vsItem.Properties.AddProperty(CompletionSource.RoslynItem, roslynItem); + } + + return roslynItem; + } + private static bool TryCreateMatchResult( CompletionHelper completionHelper, VSCompletionItem item, @@ -551,14 +569,7 @@ private static bool TryCreateMatchResult( ref int currentIndex, out MatchResult matchResult) { - if (!item.Properties.TryGetProperty(CompletionSource.RoslynItem, out RoslynCompletionItem roslynItem)) - { - roslynItem = RoslynCompletionItem.Create( - displayText: item.DisplayText, - filterText: item.FilterText, - sortText: item.SortText, - displayTextSuffix: item.Suffix); - } + var roslynItem = GetOrAddRoslynCompletionItem(item); // Get the match of the given completion item for the pattern provided so far. // A completion item is checked against the pattern by see if it's @@ -764,36 +775,5 @@ private static bool IsPotentialFilterCharacter(char c) || char.IsNumber(c) || c == '_'; } - - /// - /// This comparer can handle expanded items. - /// - private class CompletionItemSortingComparer : IComparer - { - public static CompletionItemSortingComparer Instance { get; } = new CompletionItemSortingComparer(); - - public int Compare(VSCompletionItem x, VSCompletionItem y) - { - var xHasPrefix = HasExpandedPrefix(x); - var yHasPrefix = HasExpandedPrefix(y); - - if (xHasPrefix == yHasPrefix) - { - // Don't use Ordinal because we want lowercase to be "smaller" than uppercase. - // e.g. "Goo" before "GoTo" - return string.Compare(x.SortText, y.SortText, StringComparison.InvariantCulture); - } - else if (xHasPrefix) - { - return 1; - } - else - { - return -1; - } - - static bool HasExpandedPrefix(VSCompletionItem i) => i.SortText.Length > 1 && i.SortText[0] == ImportCompletionItem.SortTextPrefix; - } - } } } diff --git a/src/Features/Core/Portable/Completion/CompletionItem.cs b/src/Features/Core/Portable/Completion/CompletionItem.cs index 49b7b00cb110e..c3702f77b66da 100644 --- a/src/Features/Core/Portable/Completion/CompletionItem.cs +++ b/src/Features/Core/Portable/Completion/CompletionItem.cs @@ -368,13 +368,28 @@ public CompletionItem WithRules(CompletionItemRules rules) int IComparable.CompareTo(CompletionItem other) { - var result = StringComparer.OrdinalIgnoreCase.Compare(SortText, other.SortText); - if (result == 0) + // Make sure expanded items are listed after non-expanded ones + var thisIsExpandItem = Flags.IsExpanded(); + var otherIsExpandItem = other.Flags.IsExpanded(); + + if (thisIsExpandItem == otherIsExpandItem) { - result = StringComparer.OrdinalIgnoreCase.Compare(GetEntireDisplayText(), other.GetEntireDisplayText()); - } + var result = StringComparer.OrdinalIgnoreCase.Compare(SortText, other.SortText); + if (result == 0) + { + result = StringComparer.OrdinalIgnoreCase.Compare(GetEntireDisplayText(), other.GetEntireDisplayText()); + } - return result; + return result; + } + else if (thisIsExpandItem) + { + return 1; + } + else + { + return -1; + } } internal string GetEntireDisplayText() diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs index 5a8eacf352222..462fd82f964d4 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ImportCompletionItem.cs @@ -16,8 +16,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers { internal static class ImportCompletionItem { - public const char SortTextPrefix = '~'; - private static readonly string s_sortTextFormat = $"{SortTextPrefix}{0} {1}"; + private const string SortTextFormat = "{0} {1}"; private const string TypeAritySuffixName = nameof(TypeAritySuffixName); private const string AttributeFullName = nameof(AttributeFullName); @@ -48,12 +47,11 @@ public static CompletionItem Create(string name, int arity, string containingNam properties = builder.ToImmutableDictionaryAndFree(); } - // Add tildes (ASCII: 126) to name and namespace as sort text: - // 1. '~' before type name makes import items show after in-scope items - // 2. ' ' before namespace makes types with identical type name but from different namespace all show up in the list, - // it also makes sure type with shorter name shows first, e.g. 'SomeType` before 'SomeTypeWithLongerName'. + // Use " " as sort text. The space before namespace makes items with identical display name + // but from different namespace all show up in the list, it also makes sure item with shorter name shows first, + // e.g. 'SomeType` before 'SomeTypeWithLongerName'. var sortTextBuilder = PooledStringBuilder.GetInstance(); - sortTextBuilder.Builder.AppendFormat(s_sortTextFormat, name, containingNamespace); + sortTextBuilder.Builder.AppendFormat(SortTextFormat, name, containingNamespace); var item = CompletionItem.Create( displayText: name, @@ -77,7 +75,7 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att var newProperties = attributeItem.Properties.Add(AttributeFullName, attributeItem.DisplayText); var sortTextBuilder = PooledStringBuilder.GetInstance(); - sortTextBuilder.Builder.AppendFormat(s_sortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription); + sortTextBuilder.Builder.AppendFormat(SortTextFormat, attributeNameWithoutSuffix, attributeItem.InlineDescription); var item = CompletionItem.Create( displayText: attributeNameWithoutSuffix, From 0a766670bafd9d0307595586f8c8525655aeff4f Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Mon, 24 Feb 2020 14:53:25 -0800 Subject: [PATCH 07/11] Fix nuget version conflict warnings --- eng/Versions.props | 6 +++--- .../Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj | 1 + .../Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj | 1 + .../Core/Microsoft.CodeAnalysis.EditorFeatures.csproj | 1 + src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj | 4 ++++ src/Tools/AnalyzerRunner/AnalyzerRunner.csproj | 2 +- .../Roslyn.Services.UnitTests.Utilities.csproj | 1 + 7 files changed, 12 insertions(+), 4 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 0200a84dab25c..2f7c1137be3f3 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -153,8 +153,8 @@ 12.0.30110 12.1.30328 16.0.0 - 16.4.16 - 16.4.16 + 16.4.45 + 16.4.45 16.5.29430.44 15.5.31 2.0.0-rc3-61304-01 @@ -233,7 +233,7 @@ create a test insertion in Visual Studio to validate. --> 12.0.2 - 2.2.34 + 2.3.99 1.5.0 1.6.0 diff --git a/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj b/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj index 163c1bb518e9b..6e20243e611a0 100644 --- a/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj +++ b/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj @@ -35,6 +35,7 @@ + diff --git a/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj b/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj index 65de0f67b731a..a17793b9aeb55 100644 --- a/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj +++ b/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj @@ -48,6 +48,7 @@ + diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index dfeb35fcbe69e..28e1883c0118d 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj index 73aff4ee4e1e5..6417b02d45f11 100644 --- a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj +++ b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj @@ -121,6 +121,10 @@ <_Dependency Remove="Microsoft.Win32.Registry"/> <_Dependency Remove="System.Security.AccessControl"/> <_Dependency Remove="System.Security.Principal.Windows"/> + <_Dependency Remove="System.Security.Principal.Windows"/> + <_Dependency Remove="MessagePack"/> + <_Dependency Remove="MessagePack.Annotations"/> + <_Dependency Remove="Microsoft.Bcl.AsyncInterfaces"/> diff --git a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj index c0ecb245af1fb..039ec21fe0583 100644 --- a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj +++ b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj @@ -21,7 +21,6 @@ - @@ -50,6 +49,7 @@ + diff --git a/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj index 1bb07a8c98edd..345d572514280 100644 --- a/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj +++ b/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj @@ -36,6 +36,7 @@ + From 43eeb59b69d7046c2bced7b0c6df88d02bd6e32d Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Mon, 24 Feb 2020 16:54:15 -0800 Subject: [PATCH 08/11] Revert "Fix nuget version conflict warnings" This reverts commit 9ef11ce3bcd1ddd60391060962158c2f79215170. --- eng/Versions.props | 6 +++--- .../Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj | 1 - .../Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj | 1 - .../Core/Microsoft.CodeAnalysis.EditorFeatures.csproj | 1 - src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj | 4 ---- src/Tools/AnalyzerRunner/AnalyzerRunner.csproj | 2 +- .../Roslyn.Services.UnitTests.Utilities.csproj | 1 - 7 files changed, 4 insertions(+), 12 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 2f7c1137be3f3..0200a84dab25c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -153,8 +153,8 @@ 12.0.30110 12.1.30328 16.0.0 - 16.4.45 - 16.4.45 + 16.4.16 + 16.4.16 16.5.29430.44 15.5.31 2.0.0-rc3-61304-01 @@ -233,7 +233,7 @@ create a test insertion in Visual Studio to validate. --> 12.0.2 - 2.3.99 + 2.2.34 1.5.0 1.6.0 diff --git a/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj b/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj index 6e20243e611a0..163c1bb518e9b 100644 --- a/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj +++ b/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj @@ -35,7 +35,6 @@ - diff --git a/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj b/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj index a17793b9aeb55..65de0f67b731a 100644 --- a/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj +++ b/src/EditorFeatures/Core.Wpf/Microsoft.CodeAnalysis.EditorFeatures.Wpf.csproj @@ -48,7 +48,6 @@ - diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index 28e1883c0118d..dfeb35fcbe69e 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -37,7 +37,6 @@ - diff --git a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj index 6417b02d45f11..73aff4ee4e1e5 100644 --- a/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj +++ b/src/Setup/DevDivInsertionFiles/DevDivInsertionFiles.csproj @@ -121,10 +121,6 @@ <_Dependency Remove="Microsoft.Win32.Registry"/> <_Dependency Remove="System.Security.AccessControl"/> <_Dependency Remove="System.Security.Principal.Windows"/> - <_Dependency Remove="System.Security.Principal.Windows"/> - <_Dependency Remove="MessagePack"/> - <_Dependency Remove="MessagePack.Annotations"/> - <_Dependency Remove="Microsoft.Bcl.AsyncInterfaces"/> diff --git a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj index 039ec21fe0583..c0ecb245af1fb 100644 --- a/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj +++ b/src/Tools/AnalyzerRunner/AnalyzerRunner.csproj @@ -21,6 +21,7 @@ + @@ -49,7 +50,6 @@ - diff --git a/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj index 345d572514280..1bb07a8c98edd 100644 --- a/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj +++ b/src/Workspaces/CoreTestUtilities/Roslyn.Services.UnitTests.Utilities.csproj @@ -36,7 +36,6 @@ - From 078ed21eb01d3bba813344c9c2fb9b968b14d2ba Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Mon, 24 Feb 2020 16:54:28 -0800 Subject: [PATCH 09/11] Revert "Upgrade VS editor packages to 16.5.173-g9c5f5bc040" This reverts commit f19214b69c2fbc6aa8431e3aceeb8d805bb99c6f. --- eng/Versions.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 0200a84dab25c..ea2ef56857e5b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -31,7 +31,7 @@ 3.3.1 1.0.1-beta1.20067.1 3.5.0-beta3-20078-04 - 16.5.173-g9c5f5bc040 + 16.4.248 5.0.0-alpha1.19409.1 16.3.27-develop-gdd55e997 @@ -106,8 +106,8 @@ 15.0.27 $(VisualStudioEditorPackagesVersion) 16.0.28226-alpha - 16.5.29430.44 - 16.5.29430.44 + 16.4.29315.20-pre + 16.4.29315.20-pre 2.8.0 $(VisualStudioEditorPackagesVersion) 15.8.27812-alpha @@ -155,7 +155,7 @@ 16.0.0 16.4.16 16.4.16 - 16.5.29430.44 + 16.4.29417.175 15.5.31 2.0.0-rc3-61304-01 4.3.0 From 2bed76f677db4ea519f83cfefcd15ae482d12ef4 Mon Sep 17 00:00:00 2001 From: Gen Lu Date: Thu, 13 Feb 2020 14:22:55 -0800 Subject: [PATCH 10/11] Add tests --- .../AbstractCSharpCompletionProviderTests.cs | 8 +-- .../CrefCompletionProviderTests.cs | 6 +- .../LoadDirectiveCompletionProviderTests.cs | 4 +- ...ferenceDirectiveCompletionProviderTests.cs | 4 +- ...olCompletionProviderTests_NoInteractive.cs | 4 +- .../TypeImportCompletionProviderTests.cs | 20 +++--- ...mentationCommentCompletionProviderTests.cs | 10 +-- .../AbstractCompletionProviderTests.cs | 69 ++++++++++--------- ...tractVisualBasicCompletionProviderTests.vb | 10 +-- ...bjectInitializerCompletionProviderTests.vb | 4 +- .../OverrideCompletionProviderTests.vb | 4 +- .../SymbolCompletionProviderTests.vb | 4 +- .../XmlDocCommentCompletionProviderTests.vb | 6 +- 13 files changed, 77 insertions(+), 76 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs index c7163159934af..f03f9800e49da 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs @@ -37,13 +37,13 @@ private protected override Task BaseVerifyWorkerAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return base.VerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } private protected override async Task VerifyWorkerAsync( @@ -52,7 +52,7 @@ private protected override async Task VerifyWorkerAsync( SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, string inlineDescription = null, - List matchingFilters = null) + List matchingFilters = null, CompletionItemFlags? flags = null) { await VerifyAtPositionAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters); await VerifyInFrontOfCommentAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters); @@ -87,7 +87,7 @@ private Task VerifyInFrontOfCommentAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags: null); } private Task VerifyInFrontOfCommentAsync( diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs index 2d3e0e1d51b70..caf93f868239e 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs @@ -35,17 +35,17 @@ private protected override async Task VerifyWorkerAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { await VerifyAtPositionAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); await VerifyAtEndOfFileAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); // Items cannot be partially written if we're checking for their absence, // or if we're verifying that the list will show up (without specifying an actual item) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs index 6bd5bc6bc903e..9e6d6614a43d3 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs @@ -35,13 +35,13 @@ private protected override Task VerifyWorkerAsync( string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs index 6775d3d74fdf4..ba04367fecac8 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs @@ -40,13 +40,13 @@ private protected override Task VerifyWorkerAsync( string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs index a41cd2ec22c6d..bbbeed632fd6d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs @@ -32,13 +32,13 @@ private protected override Task VerifyWorkerAsync( string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription, List matchingFilters) + string inlineDescription, List matchingFilters, CompletionItemFlags? flags = null) { return base.VerifyWorkerAsync(code, position, expectedItemOrNull, expectedDescriptionOrNull, SourceCodeKind.Regular, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs index 87804a6185999..e24dfc8db449c 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs @@ -928,7 +928,7 @@ class Program { } }"; var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp); - await VerifyTypeImportItemExistsAsync(markup, "My", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute"); + await VerifyTypeImportItemExistsAsync(markup, "My", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute", flags: CompletionItemFlags.Expanded); await VerifyTypeImportItemIsAbsentAsync(markup, "MyAttributeWithoutSuffix", inlineDescription: "Foo"); // We intentionally ignore attribute types without proper suffix for perf reason await VerifyTypeImportItemIsAbsentAsync(markup, "MyAttribute", inlineDescription: "Foo"); await VerifyTypeImportItemIsAbsentAsync(markup, "MyClass", inlineDescription: "Foo"); @@ -988,10 +988,10 @@ class Program }"; var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp); - await VerifyTypeImportItemExistsAsync(markup, "MyAttribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute"); - await VerifyTypeImportItemExistsAsync(markup, "MyAttributeWithoutSuffix", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttributeWithoutSuffix"); + await VerifyTypeImportItemExistsAsync(markup, "MyAttribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttribute", flags: CompletionItemFlags.CachedAndExpanded); + await VerifyTypeImportItemExistsAsync(markup, "MyAttributeWithoutSuffix", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyAttributeWithoutSuffix", flags: CompletionItemFlags.CachedAndExpanded); await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo"); - await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass"); + await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass", flags: CompletionItemFlags.CachedAndExpanded); } [InlineData(SourceCodeKind.Regular)] @@ -1049,7 +1049,7 @@ class Program { } }"; var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp); - await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute"); + await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute", flags: CompletionItemFlags.CachedAndExpanded); await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo"); await VerifyTypeImportItemIsAbsentAsync(markup, "MyClass", inlineDescription: "Foo"); } @@ -1109,9 +1109,9 @@ class Program }"; var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp); - await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute"); + await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute", flags: CompletionItemFlags.Expanded); await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo"); - await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass"); + await VerifyTypeImportItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.MyClass", flags: CompletionItemFlags.CachedAndExpanded); } [InlineData(SourceCodeKind.Regular)] @@ -1174,7 +1174,7 @@ class Program var markup = CreateMarkupForProjecWithProjectReference(file2, file1, LanguageNames.CSharp, LanguageNames.VisualBasic); - await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute"); + await VerifyTypeImportItemExistsAsync(markup, "Myattribute", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo", expectedDescriptionOrNull: "class Foo.Myattribute", flags: CompletionItemFlags.Expanded); await VerifyTypeImportItemIsAbsentAsync(markup, "My", inlineDescription: "Foo"); await VerifyTypeImportItemIsAbsentAsync(markup, "MyVBClass", inlineDescription: "Foo"); } @@ -1307,9 +1307,9 @@ private static void AssertRelativeOrder(List expectedTypesInRelativeOrde } } - private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null) + private Task VerifyTypeImportItemExistsAsync(string markup, string expectedItem, int glyph, string inlineDescription, string displayTextSuffix = null, string expectedDescriptionOrNull = null, CompletionItemFlags? flags = null) { - return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull); + return VerifyItemExistsAsync(markup, expectedItem, displayTextSuffix: displayTextSuffix, glyph: glyph, inlineDescription: inlineDescription, expectedDescriptionOrNull: expectedDescriptionOrNull, flags: flags); } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 8836c3e9734a5..efaaafd24e180 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -45,18 +45,18 @@ private protected override async Task VerifyWorkerAsync( string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { // We don't need to try writing comments in from of items in doc comments. await VerifyAtPositionAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); await VerifyAtEndOfFileAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); // Items cannot be partially written if we're checking for their absence, // or if we're verifying that the list will show up (without specifying an actual item) @@ -65,12 +65,12 @@ await VerifyAtEndOfFileAsync( await VerifyAtPosition_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); await VerifyAtEndOfFile_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } } diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index 603642fb78a7b..80437252cc811 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -75,7 +75,7 @@ private protected abstract Task BaseVerifyWorkerAsync( string code, int position, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription, List matchingFilters); + string inlineDescription, List matchingFilters, CompletionItemFlags? flags); internal static CompletionHelper GetCompletionHelper(Document document) { @@ -94,7 +94,7 @@ private protected async Task CheckResultsAsync( string expectedDescriptionOrNull, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionModeItem, string displayTextSuffix, string inlineDescription, - List matchingFilters) + List matchingFilters, CompletionItemFlags? flags) { var code = (await document.GetTextAsync()).ToString(); @@ -150,7 +150,8 @@ private protected async Task CheckResultsAsync( && (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true) && (glyph.HasValue ? c.Tags.SequenceEqual(GlyphTags.GetTags((Glyph)glyph.Value)) : true) && (matchPriority.HasValue ? (int)c.Rules.MatchPriority == matchPriority.Value : true) - && (matchingFilters != null ? FiltersMatch(matchingFilters, c) : true); + && (matchingFilters != null ? FiltersMatch(matchingFilters, c) : true) + && (flags != null ? flags.Value == c.Flags : true); AssertEx.Any(items, predicate); } @@ -193,7 +194,7 @@ private Task VerifyAsync( string markup, string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionModeItem, string displayTextSuffix, - string inlineDescription, List matchingFilters) + string inlineDescription, List matchingFilters, CompletionItemFlags? flags) { var workspace = WorkspaceFixture.GetWorkspace(markup, ExportProvider); var code = WorkspaceFixture.Code; @@ -205,7 +206,7 @@ private Task VerifyAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionModeItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); } protected async Task GetCompletionListAsync(string markup, string workspaceKind = null) @@ -272,7 +273,7 @@ private protected async Task VerifyItemExistsAsync( SourceCodeKind? sourceCodeKind = null, bool usePreviousCharAsTrigger = false, int? glyph = null, int? matchPriority = null, bool? hasSuggestionModeItem = null, string displayTextSuffix = null, string inlineDescription = null, - List matchingFilters = null) + List matchingFilters = null, CompletionItemFlags? flags = null) { if (sourceCodeKind.HasValue) { @@ -280,7 +281,7 @@ await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, sourceCodeKind.Value, usePreviousCharAsTrigger, checkForAbsence: false, glyph: glyph, matchPriority: matchPriority, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, - inlineDescription: inlineDescription, matchingFilters: matchingFilters); + inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); } else { @@ -288,13 +289,13 @@ await VerifyAsync( markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Regular, usePreviousCharAsTrigger, checkForAbsence: false, glyph: glyph, matchPriority: matchPriority, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, - inlineDescription: inlineDescription, matchingFilters: matchingFilters); + inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); await VerifyAsync( markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Script, usePreviousCharAsTrigger, checkForAbsence: false, glyph: glyph, matchPriority: matchPriority, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, - inlineDescription: inlineDescription, matchingFilters: matchingFilters); + inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); } } @@ -302,16 +303,16 @@ private protected async Task VerifyItemIsAbsentAsync( string markup, string expectedItem, string expectedDescriptionOrNull = null, SourceCodeKind? sourceCodeKind = null, bool usePreviousCharAsTrigger = false, bool? hasSuggestionModeItem = null, string displayTextSuffix = null, string inlineDescription = null, - List matchingFilters = null) + List matchingFilters = null, CompletionItemFlags? flags = null) { if (sourceCodeKind.HasValue) { - await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, sourceCodeKind.Value, usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: matchingFilters); + await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, sourceCodeKind.Value, usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); } else { - await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Regular, usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: matchingFilters); - await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Script, usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: matchingFilters); + await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Regular, usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); + await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Script, usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); } } @@ -321,12 +322,12 @@ protected async Task VerifyAnyItemExistsAsync( { if (sourceCodeKind.HasValue) { - await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: sourceCodeKind.Value, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: false, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: sourceCodeKind.Value, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: false, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null, flags: null); } else { - await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Regular, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: false, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null); - await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: false, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Regular, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: false, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null, flags: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: false, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null, flags: null); } } @@ -337,12 +338,12 @@ protected async Task VerifyNoItemsExistAsync( { if (sourceCodeKind.HasValue) { - await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: sourceCodeKind.Value, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: sourceCodeKind.Value, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null, flags: null); } else { - await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Regular, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null); - await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Regular, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null, flags: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, sourceCodeKind: SourceCodeKind.Script, usePreviousCharAsTrigger: usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, inlineDescription: inlineDescription, matchingFilters: null, flags: null); } } @@ -364,7 +365,7 @@ private protected virtual async Task VerifyWorkerAsync( int? glyph, int? matchPriority, bool? hasSuggestionModeItem, string displayTextSuffix, string inlineDescription, - List matchingFilters) + List matchingFilters, CompletionItemFlags? flags) { var document1 = WorkspaceFixture.UpdateDocument(code, sourceCodeKind); @@ -373,7 +374,7 @@ await CheckResultsAsync( expectedDescriptionOrNull, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionModeItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); if (await CanUseSpeculativeSemanticModelAsync(document1, position)) { @@ -382,7 +383,7 @@ await CheckResultsAsync( document2, position, expectedItemOrNull, expectedDescriptionOrNull, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionModeItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); } } @@ -828,7 +829,7 @@ private protected Task VerifyAtPositionAsync( SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, string inlineDescription = null, - List matchingFilters = null) + List matchingFilters = null, CompletionItemFlags? flags = null) { code = code.Substring(0, position) + insertText + code.Substring(position); position += insertText.Length; @@ -837,7 +838,7 @@ private protected Task VerifyAtPositionAsync( expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } private protected Task VerifyAtPositionAsync( @@ -845,13 +846,13 @@ private protected Task VerifyAtPositionAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return VerifyAtPositionAsync( code, position, string.Empty, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } private protected async Task VerifyAtEndOfFileAsync( @@ -859,7 +860,7 @@ private protected async Task VerifyAtEndOfFileAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { // only do this if the placeholder was at the end of the text. if (code.Length != position) @@ -874,7 +875,7 @@ await BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); } private protected Task VerifyAtPosition_ItemPartiallyWrittenAsync( @@ -882,13 +883,13 @@ private protected Task VerifyAtPosition_ItemPartiallyWrittenAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return VerifyAtPositionAsync( code, position, ItemPartiallyWritten(expectedItemOrNull), usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); } private protected Task VerifyAtEndOfFileAsync( @@ -896,12 +897,12 @@ private protected Task VerifyAtEndOfFileAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return VerifyAtEndOfFileAsync(code, position, string.Empty, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + inlineDescription, matchingFilters, flags); } private protected Task VerifyAtEndOfFile_ItemPartiallyWrittenAsync( @@ -909,13 +910,13 @@ private protected Task VerifyAtEndOfFile_ItemPartiallyWrittenAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription = null, List matchingFilters = null) + string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { return VerifyAtEndOfFileAsync( code, position, ItemPartiallyWritten(expectedItemOrNull), usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters); + matchingFilters, flags); } protected void VerifyTextualTriggerCharacter( diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb index a56eb21bcb944..905762b6e100a 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb @@ -34,12 +34,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, - matchingFilters As List(Of CompletionFilter)) As Task + matchingFilters As List(Of CompletionFilter), flags As CompletionItemFlags?) As Task Return MyBase.VerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters) + matchingFilters, flags) End Function Private Protected Overrides Async Function VerifyWorkerAsync( @@ -48,7 +48,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, - matchingFilters As List(Of CompletionFilter)) As Task + matchingFilters As List(Of CompletionFilter), flags As CompletionItemFlags?) As Task ' Script/interactive support removed for now. ' TODO: Re-enable these when interactive is back in the product. If sourceCodeKind <> Microsoft.CodeAnalysis.SourceCodeKind.Regular Then @@ -58,12 +58,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Await VerifyAtPositionAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters) + matchingFilters, flags) Await VerifyAtEndOfFileAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters) + matchingFilters, flags) ' Items cannot be partially written if we're checking for their absence, ' or if we're verifying that the list will show up (without specifying an actual item) diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb index 2cf68ab86199b..444ad5889a3c9 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb @@ -22,7 +22,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, - matchingFilters As List(Of CompletionFilter)) As Task + matchingFilters As List(Of CompletionFilter), flags As CompletionItemFlags?) As Task ' Script/interactive support removed for now. ' TODO: Re-enable these when interactive is back in the product. If sourceCodeKind <> SourceCodeKind.Regular Then @@ -33,7 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters) + matchingFilters, flags) End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb index 730bcfb0322b0..05336b3891507 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb @@ -568,7 +568,7 @@ End Class.Value Dim position As Integer MarkupTestFile.GetPosition(markup.NormalizeLineEndings(), code, position) - Await BaseVerifyWorkerAsync(code, position, "[Class]()", "Sub CBase.Class()", SourceCodeKind.Regular, False, False, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) + Await BaseVerifyWorkerAsync(code, position, "[Class]()", "Sub CBase.Class()", SourceCodeKind.Regular, False, False, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) End Function @@ -589,7 +589,7 @@ End Class.Value Await BaseVerifyWorkerAsync( code, position, "[Class]", "Property CBase.Class As Integer", - SourceCodeKind.Regular, False, False, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) + SourceCodeKind.Regular, False, False, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb index b79d170011f1f..d1ca20563bc0c 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb @@ -7818,10 +7818,10 @@ End Namespace Dim position = workspace.DocumentWithCursor.CursorPosition.Value Await CheckResultsAsync(document, position, "InstanceMethod", expectedDescriptionOrNull:=Nothing, usePreviousCharAsTrigger:=False, checkForAbsence:=False, glyph:=Nothing, matchPriority:=Nothing, hasSuggestionModeItem:=Nothing, displayTextSuffix:=Nothing, inlineDescription:=Nothing, - matchingFilters:=Nothing) + matchingFilters:=Nothing, flags:=Nothing) Await CheckResultsAsync(document, position, "SharedMethod", expectedDescriptionOrNull:=Nothing, usePreviousCharAsTrigger:=False, checkForAbsence:=False, glyph:=Nothing, matchPriority:=Nothing, hasSuggestionModeItem:=Nothing, displayTextSuffix:=Nothing, inlineDescription:=Nothing, - matchingFilters:=Nothing) + matchingFilters:=Nothing, flags:=Nothing) End Using End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb index 987135c5d3216..fad9a03c6f585 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb @@ -27,9 +27,9 @@ Namespace Tests sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, - matchingFilters As List(Of CompletionFilter)) As Task - Await VerifyAtPositionAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters) - Await VerifyAtEndOfFileAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters) + matchingFilters As List(Of CompletionFilter), flags As CompletionItemFlags?) As Task + Await VerifyAtPositionAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters, flags) + Await VerifyAtEndOfFileAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters, flags) End Function Private Async Function VerifyItemsExistAsync(markup As String, ParamArray items() As String) As Task From 25c439ca1e636d4f1ac8b51c9746f3587d96c8f3 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Mon, 24 Feb 2020 17:07:32 -0800 Subject: [PATCH 11/11] Return `null` rather than `this` from INamedTypeSymbol.TupleUnderlyingType (#41828) Fixes #41702 --- .../SymbolDisplayVisitor.Types.cs | 10 +- .../Symbols/PublicModel/NamedTypeSymbol.cs | 9 +- .../Test/Emit/CodeGen/CodeGenTupleTest.cs | 173 ++++++++++++++++-- .../Core/Portable/Symbols/INamedTypeSymbol.cs | 2 +- .../SymbolDisplayVisitor.Types.vb | 8 +- .../Test/Emit/CodeGen/CodeGenTuples.vb | 106 +++++++++++ .../SymbolKey/SymbolKey.SymbolKeyWriter.cs | 2 +- 7 files changed, 285 insertions(+), 25 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 87bae836fdf14..97cb83f1d5376 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -450,12 +450,12 @@ private void AddAnonymousTypeName(INamedTypeSymbol symbol) /// private bool CanUseTupleSyntax(INamedTypeSymbol tupleSymbol) { - INamedTypeSymbol currentUnderlying = tupleSymbol.TupleUnderlyingType; if (containsModopt(tupleSymbol)) { return false; } + INamedTypeSymbol currentUnderlying = GetTupleUnderlyingTypeOrSelf(tupleSymbol); if (currentUnderlying.Arity <= 1) { return false; @@ -473,12 +473,11 @@ private bool CanUseTupleSyntax(INamedTypeSymbol tupleSymbol) return false; } - currentUnderlying = tupleSymbol.TupleUnderlyingType; + currentUnderlying = GetTupleUnderlyingTypeOrSelf(tupleSymbol); } return true; - bool containsModopt(INamedTypeSymbol symbol) { NamedTypeSymbol underlyingTypeSymbol = (symbol as Symbols.PublicModel.NamedTypeSymbol)?.UnderlyingNamedTypeSymbol; @@ -492,6 +491,11 @@ bool containsModopt(INamedTypeSymbol symbol) } } + private static INamedTypeSymbol GetTupleUnderlyingTypeOrSelf(INamedTypeSymbol type) + { + return type.TupleUnderlyingType ?? type; + } + private static bool HasNonDefaultTupleElements(INamedTypeSymbol tupleSymbol) { return tupleSymbol.TupleElements.Any(e => !e.IsDefaultTupleElement()); diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs index 39100610e8a28..ea2bfe70d22e0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.Symbols.PublicModel { @@ -162,14 +163,18 @@ ImmutableArray INamedTypeSymbol.TupleElements } /// - /// If this is a tuple type symbol, returns the symbol for its underlying type. + /// If this is a tuple type with element names, returns the symbol for the tuple type without names. /// Otherwise, returns null. /// INamedTypeSymbol INamedTypeSymbol.TupleUnderlyingType { get { - return UnderlyingNamedTypeSymbol.TupleUnderlyingType.GetPublicSymbol(); + var type = UnderlyingNamedTypeSymbol; + var tupleUnderlyingType = type.TupleUnderlyingType; + return type.Equals(tupleUnderlyingType, TypeCompareKind.ConsiderEverything) ? + null : + tupleUnderlyingType.GetPublicSymbol(); } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs index 6e3ddede3389d..012f29418575d 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; using static TestResources.NetFX.ValueTuple; @@ -5816,7 +5817,8 @@ public struct ValueTuple var vt0 = comp.GetWellKnownType(WellKnownType.System_ValueTuple); var tupleWithoutNames = comp.CreateTupleTypeSymbol(vt0, ImmutableArray.Empty); Assert.True(tupleWithoutNames.IsTupleType); - Assert.Equal(SymbolKind.NamedType, tupleWithoutNames.TupleUnderlyingType.Kind); + Assert.Equal(SymbolKind.NamedType, tupleWithoutNames.Kind); + Assert.Null(tupleWithoutNames.TupleUnderlyingType); Assert.Equal("System.ValueTuple", tupleWithoutNames.ToTestDisplayString()); Assert.True(GetTupleElementNames(tupleWithoutNames).IsDefault); Assert.Empty(ElementTypeNames(tupleWithoutNames)); @@ -5873,7 +5875,8 @@ public void CreateTupleTypeSymbol_WithValueTuple() Assert.Same(vt2, ((Symbols.PublicModel.NamedTypeSymbol)tupleWithoutNames).UnderlyingNamedTypeSymbol); Assert.True(tupleWithoutNames.IsTupleType); - Assert.Equal(SymbolKind.NamedType, tupleWithoutNames.TupleUnderlyingType.Kind); + Assert.Equal(SymbolKind.NamedType, tupleWithoutNames.Kind); + Assert.Null(tupleWithoutNames.TupleUnderlyingType); Assert.Equal("(System.Int32, System.String)", tupleWithoutNames.ToTestDisplayString()); Assert.True(GetTupleElementNames(tupleWithoutNames).IsDefault); Assert.Equal(new[] { "System.Int32", "System.String" }, ElementTypeNames(tupleWithoutNames)); @@ -5950,7 +5953,8 @@ public void CreateTupleTypeSymbol_NoNames() var tupleWithoutNames = comp.CreateTupleTypeSymbol(vt2, default(ImmutableArray)); Assert.True(tupleWithoutNames.IsTupleType); - Assert.Equal(SymbolKind.ErrorType, tupleWithoutNames.TupleUnderlyingType.Kind); + Assert.Equal(SymbolKind.ErrorType, tupleWithoutNames.Kind); + Assert.Null(tupleWithoutNames.TupleUnderlyingType); Assert.Equal("(System.Int32, System.String)[missing]", tupleWithoutNames.ToTestDisplayString()); Assert.True(GetTupleElementNames(tupleWithoutNames).IsDefault); Assert.Equal(new[] { "System.Int32", "System.String" }, ElementTypeNames(tupleWithoutNames)); @@ -6631,7 +6635,8 @@ public void CreateTupleTypeSymbol_UnderlyingType_DefaultArgs_01() }"; var comp = (Compilation)CreateCompilation(source); var tuple1 = (INamedTypeSymbol)((IFieldSymbol)comp.GetMember("Program.F")).Type; - var underlyingType = tuple1.TupleUnderlyingType; + Assert.Null(tuple1.TupleUnderlyingType); + var underlyingType = tuple1; var tuple2 = comp.CreateTupleTypeSymbol(underlyingType); Assert.True(tuple1.Equals(tuple2, TypeCompareKind.ConsiderEverything)); @@ -6672,7 +6677,8 @@ public void CreateTupleTypeSymbol_UnderlyingType_DefaultArgs_02() }"; var comp = (Compilation)CreateCompilation(source); var tuple1 = (INamedTypeSymbol)((IFieldSymbol)comp.GetMember("Program.F")).Type; - var underlyingType = tuple1.TupleUnderlyingType; + Assert.Null(tuple1.TupleUnderlyingType); + var underlyingType = tuple1; var tuple2 = comp.CreateTupleTypeSymbol(underlyingType); Assert.True(tuple1.Equals(tuple2, TypeCompareKind.ConsiderEverything)); @@ -6789,7 +6795,8 @@ public void CreateTupleTypeSymbol_UnderlyingType_WithNullableAnnotations_01() }"; var comp = CreateCompilation(source); var tuple1 = (INamedTypeSymbol)((FieldSymbol)comp.GetMember("Program.F")).GetPublicSymbol().Type; - var underlyingType = tuple1.TupleUnderlyingType; + Assert.Null(tuple1.TupleUnderlyingType); + var underlyingType = tuple1; var tuple2 = comp.CreateTupleTypeSymbol(underlyingType, elementNullableAnnotations: default); Assert.True(tuple1.Equals(tuple2, TypeCompareKind.ConsiderEverything)); @@ -16100,7 +16107,7 @@ void M() Assert.True(xSymbol.IsTupleType); Assert.Equal("(System.Int32, System.Int32)[missing]", xSymbol.ToTestDisplayString()); - Assert.True(xSymbol.TupleUnderlyingType.IsErrorType()); + Assert.Null(xSymbol.TupleUnderlyingType); Assert.True(xSymbol.IsErrorType()); Assert.True(xSymbol.IsReferenceType); @@ -18506,10 +18513,10 @@ public void ClassifyConversionImplicit03u() ITypeSymbol stringType = comp.GetSpecialType(SpecialType.System_String); ITypeSymbol objectType = comp.GetSpecialType(SpecialType.System_Object); - var int_string1 = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)).TupleUnderlyingType; + var int_string1 = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)); var int_string2 = tupleComp2.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)); - var int_object = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, objectType)); + Assert.Null(int_string1.TupleUnderlyingType); Assert.Equal(ConversionKind.ImplicitTuple, comp.ClassifyConversion(int_string1, int_string2).Kind); Assert.Equal(ConversionKind.ImplicitTuple, comp.ClassifyConversion(int_string2, int_string1).Kind); @@ -18531,10 +18538,11 @@ public void ClassifyConversionImplicit03uu() ITypeSymbol stringType = comp.GetSpecialType(SpecialType.System_String); ITypeSymbol objectType = comp.GetSpecialType(SpecialType.System_Object); - var int_string1 = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)).TupleUnderlyingType; - var int_string2 = tupleComp2.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)).TupleUnderlyingType; - + var int_string1 = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)); + var int_string2 = tupleComp2.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)); var int_object = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, objectType)); + Assert.Null(int_string1.TupleUnderlyingType); + Assert.Null(int_string2.TupleUnderlyingType); Assert.Equal(ConversionKind.ImplicitTuple, comp.ClassifyConversion(int_string1, int_string2).Kind); Assert.Equal(ConversionKind.ImplicitTuple, comp.ClassifyConversion(int_string2, int_string1).Kind); @@ -18556,10 +18564,12 @@ public void ClassifyConversionImplicit03uuu() ITypeSymbol stringType = comp.GetSpecialType(SpecialType.System_String); ITypeSymbol objectType = comp.GetSpecialType(SpecialType.System_Object); - var int_string1 = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)).TupleUnderlyingType; - var int_string2 = tupleComp2.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)).TupleUnderlyingType; - - var int_object = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, objectType)).TupleUnderlyingType; + var int_string1 = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)); + var int_string2 = tupleComp2.CreateTupleTypeSymbol(ImmutableArray.Create(intType, stringType)); + var int_object = tupleComp1.CreateTupleTypeSymbol(ImmutableArray.Create(intType, objectType)); + Assert.Null(int_string1.TupleUnderlyingType); + Assert.Null(int_string2.TupleUnderlyingType); + Assert.Null(int_object.TupleUnderlyingType); Assert.Equal(ConversionKind.ImplicitTuple, comp.ClassifyConversion(int_string1, int_string2).Kind); Assert.Equal(ConversionKind.ImplicitTuple, comp.ClassifyConversion(int_string2, int_string1).Kind); @@ -26579,5 +26589,136 @@ public static void Main() var comp6 = CreateCompilation(source2, targetFramework: TargetFramework.Mscorlib46, options: TestOptions.DebugExe, references: comp1ImageRef); CompileAndVerify(comp6, expectedOutput: "123"); } + + [Fact] + [WorkItem(41702, "https://github.com/dotnet/roslyn/issues/41702")] + public void TupleUnderlyingType_FromCSharp() + { + var source = +@"#pragma warning disable 169 +class Program +{ + static System.ValueTuple F0; + static (int, int) F1; + static (int A, int B) F2; + static (object, object, object, object, object, object, object, object) F3; + static (object, object B, object, object D, object, object F, object, object H) F4; +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + var containingType = comp.GlobalNamespace.GetTypeMembers("Program").Single(); + VerifyTypeFromCSharp((NamedTypeSymbol)((FieldSymbol)containingType.GetMembers("F0").Single()).Type, TupleUnderlyingTypeValue.Same, TupleUnderlyingTypeValue.Null, TupleUnderlyingTypeValue.Same, TupleUnderlyingTypeValue.Null, "System.ValueTuple", "()"); + VerifyTypeFromCSharp((NamedTypeSymbol)((FieldSymbol)containingType.GetMembers("F1").Single()).Type, TupleUnderlyingTypeValue.Same, TupleUnderlyingTypeValue.Null, TupleUnderlyingTypeValue.Same, TupleUnderlyingTypeValue.Null, "(System.Int32, System.Int32)", "(System.Int32, System.Int32)"); + VerifyTypeFromCSharp((NamedTypeSymbol)((FieldSymbol)containingType.GetMembers("F2").Single()).Type, TupleUnderlyingTypeValue.Distinct, TupleUnderlyingTypeValue.Distinct, TupleUnderlyingTypeValue.Same, TupleUnderlyingTypeValue.Null, "(System.Int32 A, System.Int32 B)", "(A As System.Int32, B As System.Int32)"); + VerifyTypeFromCSharp((NamedTypeSymbol)((FieldSymbol)containingType.GetMembers("F3").Single()).Type, TupleUnderlyingTypeValue.Same, TupleUnderlyingTypeValue.Null, TupleUnderlyingTypeValue.Null, TupleUnderlyingTypeValue.Null, "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)", "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)"); + VerifyTypeFromCSharp((NamedTypeSymbol)((FieldSymbol)containingType.GetMembers("F4").Single()).Type, TupleUnderlyingTypeValue.Distinct, TupleUnderlyingTypeValue.Distinct, TupleUnderlyingTypeValue.Null, TupleUnderlyingTypeValue.Null, "(System.Object, System.Object B, System.Object, System.Object D, System.Object, System.Object F, System.Object, System.Object H)", "(System.Object, B As System.Object, System.Object, D As System.Object, System.Object, F As System.Object, System.Object, H As System.Object)"); + } + + [Fact] + [WorkItem(41702, "https://github.com/dotnet/roslyn/issues/41702")] + public void TupleUnderlyingType_FromVisualBasic() + { + var source = +@"Class Program + Private F0 As System.ValueTuple + Private F1 As (Integer, Integer) + Private F2 As (A As Integer, B As Integer) + Private F3 As (Object, Object, Object, Object, Object, Object, Object, Object) + Private F4 As (Object, B As Object, Object, D As Object, Object, F As Object, Object, H As Object) +End Class"; + var comp = CreateVisualBasicCompilation(source, referencedAssemblies: TargetFrameworkUtil.GetReferences(TargetFramework.Standard)); + comp.VerifyDiagnostics(); + var containingType = comp.GlobalNamespace.GetTypeMembers("Program").Single(); + VerifyTypeFromVisualBasic((INamedTypeSymbol)((IFieldSymbol)containingType.GetMembers("F0").Single()).Type, TupleUnderlyingTypeValue.Null, "System.ValueTuple", "System.ValueTuple"); + VerifyTypeFromVisualBasic((INamedTypeSymbol)((IFieldSymbol)containingType.GetMembers("F1").Single()).Type, TupleUnderlyingTypeValue.Distinct, "(System.Int32, System.Int32)", "(System.Int32, System.Int32)"); + VerifyTypeFromVisualBasic((INamedTypeSymbol)((IFieldSymbol)containingType.GetMembers("F2").Single()).Type, TupleUnderlyingTypeValue.Distinct, "(System.Int32 A, System.Int32 B)", "(A As System.Int32, B As System.Int32)"); + VerifyTypeFromVisualBasic((INamedTypeSymbol)((IFieldSymbol)containingType.GetMembers("F3").Single()).Type, TupleUnderlyingTypeValue.Distinct, "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)", "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)"); + VerifyTypeFromVisualBasic((INamedTypeSymbol)((IFieldSymbol)containingType.GetMembers("F4").Single()).Type, TupleUnderlyingTypeValue.Distinct, "(System.Object, System.Object B, System.Object, System.Object D, System.Object, System.Object F, System.Object, System.Object H)", "(System.Object, B As System.Object, System.Object, D As System.Object, System.Object, F As System.Object, System.Object, H As System.Object)"); + } + + private enum TupleUnderlyingTypeValue + { + Null, + Distinct, + Same + } + + private static void VerifyTypeFromCSharp( + NamedTypeSymbol type, + TupleUnderlyingTypeValue expectedInternalValue, + TupleUnderlyingTypeValue expectedPublicValue, + TupleUnderlyingTypeValue definitionInternalValue, + TupleUnderlyingTypeValue definitionPublicValue, + string expectedCSharp, + string expectedVisualBasic) + { + VerifyDisplay(type.GetPublicSymbol(), expectedCSharp, expectedVisualBasic); + VerifyInternalType(type, expectedInternalValue); + VerifyPublicType(type.GetPublicSymbol(), expectedPublicValue); + type = type.OriginalDefinition; + VerifyInternalType(type, definitionInternalValue); + VerifyPublicType(type.GetPublicSymbol(), definitionPublicValue); + } + + private static void VerifyTypeFromVisualBasic( + INamedTypeSymbol type, + TupleUnderlyingTypeValue expectedValue, + string expectedCSharp, + string expectedVisualBasic) + { + VerifyDisplay(type, expectedCSharp, expectedVisualBasic); + VerifyPublicType(type, expectedValue); + VerifyPublicType(type.OriginalDefinition, expectedValue); + } + + private static void VerifyDisplay(INamedTypeSymbol type, string expectedCSharp, string expectedVisualBasic) + { + Assert.Equal(expectedCSharp, CSharp.SymbolDisplay.ToDisplayString(type, SymbolDisplayFormat.TestFormat)); + Assert.Equal(expectedVisualBasic, VisualBasic.SymbolDisplay.ToDisplayString(type, SymbolDisplayFormat.TestFormat)); + } + + private static void VerifyInternalType(NamedTypeSymbol type, TupleUnderlyingTypeValue expectedValue) + { + var underlyingType = type.TupleUnderlyingType; + + switch (expectedValue) + { + case TupleUnderlyingTypeValue.Null: + Assert.Null(underlyingType); + break; + case TupleUnderlyingTypeValue.Distinct: + Assert.NotEqual(type, underlyingType); + Assert.True(type.Equals(underlyingType, TypeCompareKind.AllIgnoreOptions)); + Assert.False(type.Equals(underlyingType, TypeCompareKind.ConsiderEverything)); + VerifyInternalType(underlyingType, TupleUnderlyingTypeValue.Same); + break; + case TupleUnderlyingTypeValue.Same: + Assert.Equal(type, underlyingType); + Assert.True(type.Equals(underlyingType, TypeCompareKind.ConsiderEverything)); + break; + default: + throw ExceptionUtilities.UnexpectedValue(expectedValue); + } + } + + private static void VerifyPublicType(INamedTypeSymbol type, TupleUnderlyingTypeValue expectedValue) + { + var underlyingType = type.TupleUnderlyingType; + + switch (expectedValue) + { + case TupleUnderlyingTypeValue.Null: + Assert.Null(underlyingType); + break; + case TupleUnderlyingTypeValue.Distinct: + Assert.NotEqual(type, underlyingType); + Assert.False(type.Equals(underlyingType, SymbolEqualityComparer.Default)); + Assert.False(type.Equals(underlyingType, SymbolEqualityComparer.ConsiderEverything)); + VerifyPublicType(underlyingType, TupleUnderlyingTypeValue.Null); + break; + default: + throw ExceptionUtilities.UnexpectedValue(expectedValue); + } + } } } diff --git a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs index 9f378dbddc606..2f6a5461755b3 100644 --- a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs @@ -167,7 +167,7 @@ public interface INamedTypeSymbol : ITypeSymbol bool MightContainExtensionMethods { get; } /// - /// If this is a tuple type symbol, returns the symbol for its underlying type (ie. without element names). + /// If this is a tuple type with element names, returns the symbol for the tuple type without names. /// Otherwise, returns null. /// The type argument corresponding to the type of the extension field (VT[8].Rest), /// which is at the 8th (one based) position is always a symbol for another tuple, diff --git a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb index ff51f92a9647c..cb825319dbf69 100644 --- a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb +++ b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb @@ -330,7 +330,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ''' ''' Private Shared Function CanUseTupleTypeName(tupleSymbol As INamedTypeSymbol) As Boolean - Dim currentUnderlying As INamedTypeSymbol = tupleSymbol.TupleUnderlyingType + Dim currentUnderlying As INamedTypeSymbol = GetTupleUnderlyingTypeOrSelf(tupleSymbol) If currentUnderlying.Arity = 1 Then Return False @@ -344,12 +344,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return False End If - currentUnderlying = tupleSymbol.TupleUnderlyingType + currentUnderlying = GetTupleUnderlyingTypeOrSelf(tupleSymbol) End While Return True End Function + Private Shared Function GetTupleUnderlyingTypeOrSelf(tupleSymbol As INamedTypeSymbol) As INamedTypeSymbol + Return If(tupleSymbol.TupleUnderlyingType, tupleSymbol) + End Function + Private Shared Function HasNonDefaultTupleElements(tupleSymbol As INamedTypeSymbol) As Boolean Return tupleSymbol.TupleElements.Any(Function(e) Not e.IsDefaultTupleElement) End Function diff --git a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb index 7af45489db9cf..64f421e25d68b 100644 --- a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb +++ b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenTuples.vb @@ -22944,6 +22944,112 @@ End Class CompileAndVerify(comp6, expectedOutput:="123") End Sub + + + Public Sub TupleUnderlyingType_FromCSharp() + Dim source = +"#pragma warning disable 169 +class Program +{ + static System.ValueTuple F0; + static (int, int) F1; + static (int A, int B) F2; + static (object, object, object, object, object, object, object, object) F3; + static (object, object B, object, object D, object, object F, object, object H) F4; +}" + Dim comp = CreateCSharpCompilation(source, referencedAssemblies:=TargetFrameworkUtil.GetReferences(TargetFramework.Standard)) + comp.VerifyDiagnostics() + Dim containingType = comp.GlobalNamespace.GetTypeMembers("Program").Single() + VerifyTypeFromCSharp(DirectCast(DirectCast(containingType.GetMembers("F0").Single(), IFieldSymbol).Type, INamedTypeSymbol), TupleUnderlyingTypeValue.Nothing, "System.ValueTuple", "()") + VerifyTypeFromCSharp(DirectCast(DirectCast(containingType.GetMembers("F1").Single(), IFieldSymbol).Type, INamedTypeSymbol), TupleUnderlyingTypeValue.Nothing, "(System.Int32, System.Int32)", "(System.Int32, System.Int32)") + VerifyTypeFromCSharp(DirectCast(DirectCast(containingType.GetMembers("F2").Single(), IFieldSymbol).Type, INamedTypeSymbol), TupleUnderlyingTypeValue.Distinct, "(System.Int32 A, System.Int32 B)", "(A As System.Int32, B As System.Int32)") + VerifyTypeFromCSharp(DirectCast(DirectCast(containingType.GetMembers("F3").Single(), IFieldSymbol).Type, INamedTypeSymbol), TupleUnderlyingTypeValue.Nothing, "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)", "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)") + VerifyTypeFromCSharp(DirectCast(DirectCast(containingType.GetMembers("F4").Single(), IFieldSymbol).Type, INamedTypeSymbol), TupleUnderlyingTypeValue.Distinct, "(System.Object, System.Object B, System.Object, System.Object D, System.Object, System.Object F, System.Object, System.Object H)", "(System.Object, B As System.Object, System.Object, D As System.Object, System.Object, F As System.Object, System.Object, H As System.Object)") + End Sub + + + + Public Sub TupleUnderlyingType_FromVisualBasic() + Dim source = +"Class Program + Private F0 As System.ValueTuple + Private F1 As (Integer, Integer) + Private F2 As (A As Integer, B As Integer) + Private F3 As (Object, Object, Object, Object, Object, Object, Object, Object) + Private F4 As (Object, B As Object, Object, D As Object, Object, F As Object, Object, H As Object) +End Class" + Dim comp = CreateCompilation(source) + comp.AssertNoDiagnostics() + Dim containingType = comp.GlobalNamespace.GetTypeMembers("Program").Single() + VerifyTypeFromVisualBasic(DirectCast(DirectCast(containingType.GetMembers("F0").Single(), FieldSymbol).Type, NamedTypeSymbol), TupleUnderlyingTypeValue.Nothing, "System.ValueTuple", "System.ValueTuple") + VerifyTypeFromVisualBasic(DirectCast(DirectCast(containingType.GetMembers("F1").Single(), FieldSymbol).Type, NamedTypeSymbol), TupleUnderlyingTypeValue.Distinct, "(System.Int32, System.Int32)", "(System.Int32, System.Int32)") + VerifyTypeFromVisualBasic(DirectCast(DirectCast(containingType.GetMembers("F2").Single(), FieldSymbol).Type, NamedTypeSymbol), TupleUnderlyingTypeValue.Distinct, "(System.Int32 A, System.Int32 B)", "(A As System.Int32, B As System.Int32)") + VerifyTypeFromVisualBasic(DirectCast(DirectCast(containingType.GetMembers("F3").Single(), FieldSymbol).Type, NamedTypeSymbol), TupleUnderlyingTypeValue.Distinct, "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)", "(System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object, System.Object)") + VerifyTypeFromVisualBasic(DirectCast(DirectCast(containingType.GetMembers("F4").Single(), FieldSymbol).Type, NamedTypeSymbol), TupleUnderlyingTypeValue.Distinct, "(System.Object, System.Object B, System.Object, System.Object D, System.Object, System.Object F, System.Object, System.Object H)", "(System.Object, B As System.Object, System.Object, D As System.Object, System.Object, F As System.Object, System.Object, H As System.Object)") + End Sub + + Private Enum TupleUnderlyingTypeValue + [Nothing] + Distinct + Same + End Enum + + Private Shared Sub VerifyTypeFromCSharp(type As INamedTypeSymbol, expectedValue As TupleUnderlyingTypeValue, expectedCSharp As String, expectedVisualBasic As String) + VerifyDisplay(type, expectedCSharp, expectedVisualBasic) + VerifyPublicType(type, expectedValue) + VerifyPublicType(type.OriginalDefinition, TupleUnderlyingTypeValue.Nothing) + End Sub + + Private Shared Sub VerifyTypeFromVisualBasic(type As NamedTypeSymbol, expectedValue As TupleUnderlyingTypeValue, expectedCSharp As String, expectedVisualBasic As String) + VerifyDisplay(type, expectedCSharp, expectedVisualBasic) + VerifyInternalType(type, expectedValue) + VerifyPublicType(type, expectedValue) + type = type.OriginalDefinition + VerifyInternalType(type, expectedValue) + VerifyPublicType(type, expectedValue) + End Sub + + Private Shared Sub VerifyDisplay(type As INamedTypeSymbol, expectedCSharp As String, expectedVisualBasic As String) + Assert.Equal(expectedCSharp, CSharp.SymbolDisplay.ToDisplayString(type, SymbolDisplayFormat.TestFormat)) + Assert.Equal(expectedVisualBasic, VisualBasic.SymbolDisplay.ToDisplayString(type, SymbolDisplayFormat.TestFormat)) + End Sub + + Private Shared Sub VerifyInternalType(type As NamedTypeSymbol, expectedValue As TupleUnderlyingTypeValue) + Dim underlyingType = type.TupleUnderlyingType + + Select Case expectedValue + Case TupleUnderlyingTypeValue.Nothing + Assert.Null(underlyingType) + + Case TupleUnderlyingTypeValue.Distinct + Assert.NotEqual(type, underlyingType) + Assert.False(type.Equals(underlyingType, TypeCompareKind.AllIgnoreOptions)) + Assert.False(type.Equals(underlyingType, TypeCompareKind.ConsiderEverything)) + VerifyPublicType(underlyingType, expectedValue:=TupleUnderlyingTypeValue.Nothing) + + Case Else + Throw ExceptionUtilities.UnexpectedValue(expectedValue) + End Select + End Sub + + Private Shared Sub VerifyPublicType(type As INamedTypeSymbol, expectedValue As TupleUnderlyingTypeValue) + Dim underlyingType = type.TupleUnderlyingType + + Select Case expectedValue + Case TupleUnderlyingTypeValue.Nothing + Assert.Null(underlyingType) + + Case TupleUnderlyingTypeValue.Distinct + Assert.NotEqual(type, underlyingType) + Assert.False(type.Equals(underlyingType, SymbolEqualityComparer.Default)) + Assert.False(type.Equals(underlyingType, SymbolEqualityComparer.ConsiderEverything)) + VerifyPublicType(underlyingType, expectedValue:=TupleUnderlyingTypeValue.Nothing) + + Case Else + Throw ExceptionUtilities.UnexpectedValue(expectedValue) + End Select + End Sub + End Class End Namespace diff --git a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.SymbolKeyWriter.cs b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.SymbolKeyWriter.cs index 3d841b5948c8a..97a589985197c 100644 --- a/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.SymbolKeyWriter.cs +++ b/src/Workspaces/Core/Portable/SymbolKey/SymbolKey.SymbolKeyWriter.cs @@ -422,7 +422,7 @@ public override object VisitNamedType(INamedTypeSymbol namedTypeSymbol) WriteType(SymbolKeyType.ErrorType); ErrorTypeSymbolKey.Create(namedTypeSymbol, this); } - else if (namedTypeSymbol.IsTupleType && namedTypeSymbol.TupleUnderlyingType != namedTypeSymbol) + else if (namedTypeSymbol.IsTupleType && namedTypeSymbol.TupleUnderlyingType is INamedTypeSymbol underlyingType && underlyingType != namedTypeSymbol) { // A tuple is a named type with some added information // We only need to store this extra information if there is some