diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs index 43ec5c48ef04d..bb8bfd3db76ec 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs @@ -38,13 +38,14 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, 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, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); } private protected override async Task VerifyWorkerAsync( @@ -52,20 +53,20 @@ 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, + bool? hasSuggestionItem, string displayTextSuffix, string displayTextPrefix, string inlineDescription = 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); - await VerifyAtEndOfFileAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters); + await VerifyAtPositionAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters); + await VerifyInFrontOfCommentAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters); + await VerifyAtEndOfFileAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters); // 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) if (!checkForAbsence && expectedItemOrNull != null) { - await VerifyAtPosition_ItemPartiallyWrittenAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters); - await VerifyInFrontOfComment_ItemPartiallyWrittenAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters); - await VerifyAtEndOfFile_ItemPartiallyWrittenAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, matchingFilters); + await VerifyAtPosition_ItemPartiallyWrittenAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters); + await VerifyInFrontOfComment_ItemPartiallyWrittenAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters); + await VerifyAtEndOfFile_ItemPartiallyWrittenAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters); } } @@ -77,7 +78,7 @@ private Task VerifyInFrontOfCommentAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription, List matchingFilters) + string displayTextPrefix, string inlineDescription, List matchingFilters) { code = code.Substring(0, position) + insertText + "/**/" + code.Substring(position); position += insertText.Length; @@ -85,8 +86,8 @@ private Task VerifyInFrontOfCommentAsync( return base.VerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, - matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters, flags: null); + matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, + inlineDescription, matchingFilters, flags: null); } private Task VerifyInFrontOfCommentAsync( @@ -94,13 +95,13 @@ private Task VerifyInFrontOfCommentAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription, List matchingFilters) + string displayTextPrefix, string inlineDescription, List matchingFilters) { return VerifyInFrontOfCommentAsync( code, position, string.Empty, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + displayTextPrefix, inlineDescription, matchingFilters); } private protected Task VerifyInFrontOfComment_ItemPartiallyWrittenAsync( @@ -108,13 +109,13 @@ private protected Task VerifyInFrontOfComment_ItemPartiallyWrittenAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, string displayTextSuffix, - string inlineDescription, List matchingFilters) + string displayTextPrefix, string inlineDescription, List matchingFilters) { return VerifyInFrontOfCommentAsync( code, position, ItemPartiallyWritten(expectedItemOrNull), usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + displayTextPrefix, inlineDescription, matchingFilters); } protected static string AddInsideMethod(string text) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs index 1e9290fe68113..efb842b49c7e6 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CompletionProviderOrderTests.cs @@ -40,6 +40,7 @@ public void TestCompletionProviderOrder() typeof(KeywordCompletionProvider), typeof(SpeculativeTCompletionProvider), typeof(SymbolCompletionProvider), + typeof(UnnamedSymbolCompletionProvider), typeof(ExplicitInterfaceMemberCompletionProvider), typeof(ExplicitInterfaceTypeCompletionProvider), typeof(ObjectCreationCompletionProvider), diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs new file mode 100644 index 0000000000000..236b150d2f360 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ConversionCompletionProviderTests.cs @@ -0,0 +1,1566 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; +using Roslyn.Test.Utilities; +using Xunit; +using CompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders +{ + public class ConversionCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + internal override Type GetCompletionProviderType() + => typeof(UnnamedSymbolCompletionProvider); + + private static string FormatExplicitConversionDescription(string fromType, string toType) + => string.Format(WorkspacesResources.Predefined_conversion_from_0_to_1, fromType, toType); + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorIndexerCompletionItemsShouldBePlacedLastInCompletionList() + { + var castCompletionItem = (await GetCompletionItemsAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", SourceCodeKind.Regular)).Single(); + + var completionList = new[] { + CompletionItem.Create("SomeText0"), + castCompletionItem, + CompletionItem.Create("SomeText1"), + CompletionItem.Create("\uffdcStartWith_FFDC_Identifier"), // http://www.fileformat.info/info/unicode/char/ffdc/index.htm + CompletionItem.Create("SomeText2"), + CompletionItem.Create("\uD884\uDF4AStartWith_3134A_Identifier"), // http://www.fileformat.info/info/unicode/char/3134a/index.htm + CompletionItem.Create("SomeText3"), + }; + Array.Sort(completionList); + Assert.Collection(completionList, + c => Assert.Equal("SomeText0", c.DisplayText), + c => Assert.Equal("SomeText1", c.DisplayText), + c => Assert.Equal("SomeText2", c.DisplayText), + c => Assert.Equal("SomeText3", c.DisplayText), + c => Assert.Equal("\uD884\uDF4AStartWith_3134A_Identifier", c.DisplayText), + c => Assert.Equal("\uffdcStartWith_FFDC_Identifier", c.DisplayText), + c => + { + Assert.Same(c, castCompletionItem); + Assert.Equal("float", c.DisplayText); + Assert.Equal("\uFFFD001_float", c.SortText); + Assert.Equal("float", c.FilterText); + }); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsNotOfferedAfterNumberLiteral() + { + // User may want to type a floating point literal. + await VerifyNoItemsExistAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + 1.$$ + } +} +", SourceCodeKind.Regular); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsSuggestedAfterDot() + { + await VerifyItemExistsAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", "float", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsSuggestedIfMemberNameIsPartiallyWritten() + { + await VerifyItemExistsAsync(@" +public class C +{ + public void fly() { } + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.fl$$ + } +} +", "float", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("c.$$", true)] + [InlineData("c.fl$$", true)] + [InlineData("c. $$", true)] + [InlineData("c.fl $$", false)] + [InlineData("c.($$", false)] + [InlineData("c$$", false)] + [InlineData(@"""c.$$", false)] + [InlineData("c?.$$", true)] + [InlineData("((C)c).$$", true)] + [InlineData("(true ? c : c).$$", true)] + [InlineData("c.$$ var x=0;", false)] + public async Task ExplicitUserDefinedConversionDifferentExpressions(string expression, bool shouldSuggestConversion) + { + Func verifyFunc = shouldSuggestConversion + ? (markup, expectedItem) => VerifyItemExistsAsync(markup, expectedItem, displayTextPrefix: "(", displayTextSuffix: ")") + : (markup, expectedItem) => VerifyItemIsAbsentAsync(markup, expectedItem); + + await verifyFunc(@$" +public class C +{{ + public static explicit operator float(C c) => 0; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {expression} + }} +}} +", "float"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsNotSuggestedOnStaticAccess() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + C.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsNotSuggestedInNameofContext() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + var name = nameof(c.$$ + } +} +"); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("", "Nested1.C", "Nested2.C")] + [InlineData("using N1.Nested1;", "C", "Nested2.C")] + [InlineData("using N1.Nested2;", "C", "Nested1.C")] + [InlineData("using N1.Nested1;using N1.Nested2;", "Nested1.C", "Nested2.C")] + public async Task ExplicitUserDefinedConversionTypeDisplayStringIsMinimal(string usingDirective, string displayText1, string displayText2) + { + var items = await GetCompletionItemsAsync(@$" +namespace N1.Nested1 +{{ + public class C + {{ + }} +}} + +namespace N1.Nested2 +{{ + public class C + {{ + }} +}} +namespace N2 +{{ + public class Conversion + {{ + public static explicit operator N1.Nested1.C(Conversion _) => new N1.Nested1.C(); + public static explicit operator N1.Nested2.C(Conversion _) => new N1.Nested2.C(); + }} +}} +namespace N1 +{{ + {usingDirective} + public class Test + {{ + public void M() + {{ + var conversion = new N2.Conversion(); + conversion.$$ + }} + }} +}} +", SourceCodeKind.Regular); + Assert.Collection(items, + i => Assert.Equal(displayText1, i.DisplayText), + i => Assert.Equal(displayText2, i.DisplayText)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsSuggestedForAllExplicitConversionsToOtherTypesAndNotForImplicitConversions() + { + var items = await GetCompletionItemsAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; + public static explicit operator int(C c) => 0; + + public static explicit operator C(float f) => new C(); + public static implicit operator C(string s) => new C(); + public static implicit operator string(C c) => ""; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", SourceCodeKind.Regular); + Assert.Collection(items, + i => Assert.Equal("float", i.DisplayText), + i => Assert.Equal("int", i.DisplayText)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIgnoresConversionLikeMethods() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static bool op_Explicit(C c) => false; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIgnoreMalformedOperators() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static explicit operator int() => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionFromOtherTypeToTargetIsNotSuggested() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static explicit operator C(D d) => null; +} +public class D +{ +} + +public class Program +{ + public static void Main() + { + var d = new D(); + d.$$ + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionIsApplied() + { + await VerifyCustomCommitProviderAsync(@" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", "float", @" +public class C +{ + public static explicit operator float(C c) => 0; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + ((float)c)$$ + } +} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("white.$$", "Black", + "((Black)white)$$")] + [InlineData("white.$$;", "Black", + "((Black)white)$$;")] + [InlineData("white.Bl$$", "Black", + "((Black)white)$$")] + [InlineData("white.Bl$$;", "Black", + "((Black)white)$$;")] + [InlineData("white?.Bl$$;", "Black", + "((Black)white)?$$;")] + [InlineData("white.$$Bl;", "Black", + "((Black)white)$$Bl;")] + [InlineData("var f = white.$$;", "Black", + "var f = ((Black)white)$$;")] + [InlineData("white?.$$", "Black", + "((Black)white)?$$")] + [InlineData("white?.$$b", "Black", + "((Black)white)?$$b")] + [InlineData("white?.$$b.c()", "Black", + "((Black)white)?$$b.c()")] + [InlineData("white?.$$b()", "Black", + "((Black)white)?$$b()")] + [InlineData("white.Black?.$$", "White", + "((White)white.Black)?$$")] + [InlineData("white.Black.$$", "White", + "((White)white.Black)$$")] + [InlineData("white?.Black?.$$", "White", + "((White)white?.Black)?$$")] + [InlineData("white?.Black?.fl$$", "White", + "((White)white?.Black)?$$")] + [InlineData("white?.Black.fl$$", "White", + "((White)white?.Black)$$")] + [InlineData("white?.Black.White.Bl$$ack?.White", "Black", + "((Black)white?.Black.White)$$?.White")] + [InlineData("((White)white).$$", "Black", + "((Black)((White)white))$$")] + [InlineData("(true ? white : white).$$", "Black", + "((Black)(true ? white : white))$$")] + public async Task ExplicitUserDefinedConversionIsAppliedForDifferentExpressions(string expression, string conversionOffering, string fixedCode) + { + await VerifyCustomCommitProviderAsync($@" +namespace N +{{ + public class Black + {{ + public White White {{ get; }} + public static explicit operator White(Black _) => new White(); + }} + public class White + {{ + public Black Black {{ get; }} + public static explicit operator Black(White _) => new Black(); + }} + + public class Program + {{ + public static void Main() + {{ + var white = new White(); + {expression} + }} + }} +}} +", conversionOffering, @$" +namespace N +{{ + public class Black + {{ + public White White {{ get; }} + public static explicit operator White(Black _) => new White(); + }} + public class White + {{ + public Black Black {{ get; }} + public static explicit operator Black(White _) => new Black(); + }} + + public class Program + {{ + public static void Main() + {{ + var white = new White(); + {fixedCode} + }} + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + // Source: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types + [InlineData("bool")] + [InlineData("byte")] + [InlineData("sbyte")] + [InlineData("char")] + [InlineData("decimal")] + [InlineData("double")] + [InlineData("float")] + [InlineData("int")] + [InlineData("uint")] + [InlineData("long")] + [InlineData("ulong")] + [InlineData("short")] + [InlineData("ushort")] + [InlineData("object")] //not valid: https://docs.microsoft.com/de-de/dotnet/csharp/misc/cs0553 + [InlineData("string")] + [InlineData("dynamic")] //not valid: CS1964 conversion to or from dynamic type is not allowed + public async Task ExplicitUserDefinedConversionIsAppliedForBuiltinTypeKeywords(string builtinType) + { + await VerifyCustomCommitProviderAsync($@" +namespace N +{{ + public class C + {{ + public static explicit operator {builtinType}(C _) => default; + }} + + public class Program + {{ + public static void Main() + {{ + var c = new C(); + c.{builtinType}$$ + }} + }} +}} +", $"{builtinType}", @$" +namespace N +{{ + public class C + {{ + public static explicit operator {builtinType}(C _) => default; + }} + + public class Program + {{ + public static void Main() + {{ + var c = new C(); + (({builtinType})c)$$ + }} + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + // List derived from https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ + // Includes all keywords and contextual keywords + [InlineData("abstract")] + [InlineData("as")] + [InlineData("base")] + [InlineData("bool")] + [InlineData("break")] + [InlineData("byte")] + [InlineData("case")] + [InlineData("catch")] + [InlineData("char")] + [InlineData("checked")] + [InlineData("class")] + [InlineData("const")] + [InlineData("continue")] + [InlineData("decimal")] + [InlineData("default")] + [InlineData("delegate")] + [InlineData("do")] + [InlineData("double")] + [InlineData("else")] + [InlineData("enum")] + [InlineData("event")] + [InlineData("explicit")] + [InlineData("extern")] + [InlineData("false")] + [InlineData("finally")] + [InlineData("fixed")] + [InlineData("float")] + [InlineData("for")] + [InlineData("foreach")] + [InlineData("goto")] + [InlineData("if")] + [InlineData("implicit")] + [InlineData("in")] + [InlineData("int")] + [InlineData("interface")] + [InlineData("internal")] + [InlineData("is")] + [InlineData("lock")] + [InlineData("long")] + [InlineData("namespace")] + [InlineData("new")] + [InlineData("null")] + [InlineData("object")] + [InlineData("operator")] + [InlineData("out")] + [InlineData("override")] + [InlineData("params")] + [InlineData("private")] + [InlineData("protected")] + [InlineData("public")] + [InlineData("readonly")] + [InlineData("ref")] + [InlineData("return")] + [InlineData("sbyte")] + [InlineData("sealed")] + [InlineData("short")] + [InlineData("sizeof")] + [InlineData("stackalloc")] + [InlineData("static")] + [InlineData("string")] + [InlineData("struct")] + [InlineData("switch")] + [InlineData("this")] + [InlineData("throw")] + [InlineData("true")] + [InlineData("try")] + [InlineData("typeof")] + [InlineData("uint")] + [InlineData("ulong")] + [InlineData("unchecked")] + [InlineData("unsafe")] + [InlineData("ushort")] + [InlineData("using")] + [InlineData("virtual")] + [InlineData("void")] + [InlineData("volatile")] + [InlineData("while")] + [InlineData("add")] + [InlineData("alias")] + [InlineData("ascending")] + [InlineData("async")] + [InlineData("await")] + [InlineData("by")] + [InlineData("descending")] + [InlineData("dynamic")] + [InlineData("equals")] + [InlineData("from")] + [InlineData("get")] + [InlineData("global")] + [InlineData("group")] + [InlineData("into")] + [InlineData("join")] + [InlineData("let")] + [InlineData("nameof")] + [InlineData("notnull")] + [InlineData("on")] + [InlineData("orderby")] + [InlineData("partial")] + [InlineData("remove")] + [InlineData("select")] + [InlineData("set")] + [InlineData("unmanaged")] + [InlineData("value")] + [InlineData("var")] + [InlineData("when")] + [InlineData("where")] + [InlineData("yield")] + public async Task ExplicitUserDefinedConversionIsAppliedForOtherKeywords(string keyword) + { + await VerifyCustomCommitProviderAsync($@" +namespace N +{{ + public class {keyword}Class + {{ + }} + public class C + {{ + public static explicit operator {keyword}Class(C _) => new {keyword}Class; + }} + + public class Program + {{ + public static void Main() + {{ + var c = new C(); + c.{keyword}$$ + }} + }} +}} +", $"{keyword}Class", @$" +namespace N +{{ + public class {keyword}Class + {{ + }} + public class C + {{ + public static explicit operator {keyword}Class(C _) => new {keyword}Class; + }} + + public class Program + {{ + public static void Main() + {{ + var c = new C(); + (({keyword}Class)c)$$ + }} + }} +}} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionToGenericType() + { + await VerifyCustomCommitProviderAsync( +@" +public class C +{ + public static explicit operator D(C _) => default; +} +public class D +{ +} +public class Program +{ + public static void Main() + { + { + var c = new C(); + c.$$ + } + } +} +", "D", +@" +public class C +{ + public static explicit operator D(C _) => default; +} +public class D +{ +} +public class Program +{ + public static void Main() + { + { + var c = new C(); + ((D)c)$$ + } + } +} +" + ); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionToArray() + { + await VerifyCustomCommitProviderAsync( +@" +public class C +{ + public static explicit operator int[](C _) => default; +} +public class Program +{ + public static void Main() + { + { + var c = new C(); + c.$$ + } + } +} +", "int[]", +@" +public class C +{ + public static explicit operator int[](C _) => default; +} +public class Program +{ + public static void Main() + { + { + var c = new C(); + ((int[])c)$$ + } + } +} +" + ); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("/* Leading */c.$$", + "/* Leading */((float)c)$$")] + [InlineData("/* Leading */c.fl$$", + "/* Leading */((float)c)$$")] + [InlineData("c. $$", + "((float)c)$$ ")] + [InlineData("(true ? /* Inline */ c : c).$$", + "((float)(true ? /* Inline */ c : c))$$")] + [InlineData("c.fl$$ /* Trailing */", + "((float)c)$$ /* Trailing */")] + public async Task ExplicitUserDefinedConversionTriviaHandling(string expression, string fixedCode) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public static explicit operator float(C c) => 0; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {expression} + }} +}} +", "float", @$" +public class C +{{ + public static explicit operator float(C c) => 0; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {fixedCode} + }} +}} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitConversionOfNullableStructToNullableStructIsOffered() + { + // Lifted conversion https://docs.microsoft.com/hu-hu/dotnet/csharp/language-reference/language-specification/conversions#lifted-conversion-operators + await VerifyItemExistsAsync(@" +public struct S { + public static explicit operator int(S _) => 0; +} +public class Program +{ + public static void Main() + { + S? s = null; + s.$$ + } +} +", "int?", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitConversionOfNullableStructToNullableStructIsApplied() + { + // Lifted conversion https://docs.microsoft.com/hu-hu/dotnet/csharp/language-reference/language-specification/conversions#lifted-conversion-operators + await VerifyCustomCommitProviderAsync(@" +public struct S { + public static explicit operator int(S _) => 0; +} +public class Program +{ + public static void Main() + { + S? s = null; + s.$$ + } +} +", "int?", @" +public struct S { + public static explicit operator int(S _) => 0; +} +public class Program +{ + public static void Main() + { + S? s = null; + ((int?)s)$$ + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitConversionOfConditionalAccessOfStructAppliesNullableStruct() + { + // see https://sharplab.io/#gist:08c697b6b9b6384b8ec81cc586e064e6 to run a sample + // conversion ((int)c?.S) fails with System.InvalidOperationException: Nullable object must have a value. + // conversion ((int?)c?.S) passes (returns an int? with HasValue == false) + await VerifyCustomCommitProviderAsync(@" +public struct S { + public static explicit operator int(S _) => 0; +} +public class C { + public S S { get; } = default; +} +public class Program +{ + public static void Main() + { + C c = null; + c?.S.$$ + } +} +", "int?", @" +public struct S { + public static explicit operator int(S _) => 0; +} +public class C { + public S S { get; } = default; +} +public class Program +{ + public static void Main() + { + C c = null; + ((int?)c?.S)$$ + } +} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [CombinatorialData] + public async Task ExplicitConversionOfConditionalAccessFromClassOrStructToClassOrStruct( + [CombinatorialValues("struct", "class")] string fromClassOrStruct, + [CombinatorialValues("struct", "class")] string toClassOrStruct, + bool propertyIsNullable, + bool conditionalAccess) + { + if (fromClassOrStruct == "class" && propertyIsNullable) + { + // This test is solely about lifting of nullable value types. The CombinatorialData also + // adds cases for nullable reference types: public class From ... public From? From { get; } + // We don't want to test NRT cases here. + return; + } + + var assertShouldBeNullable = + fromClassOrStruct == "struct" && + toClassOrStruct == "struct" && + (propertyIsNullable || conditionalAccess); + + var propertyNullableQuestionMark = propertyIsNullable ? "?" : ""; + var conditionalAccessQuestionMark = conditionalAccess ? "?" : ""; + var shouldBeNullableQuestionMark = assertShouldBeNullable ? "?" : ""; + await VerifyCustomCommitProviderAsync(@$" +public {fromClassOrStruct} From {{ + public static explicit operator To(From _) => default; +}} +public {toClassOrStruct} To {{ +}} +public class C {{ + public From{propertyNullableQuestionMark} From {{ get; }} = default; +}} +public class Program +{{ + public static void Main() + {{ + C c = null; + c{conditionalAccessQuestionMark}.From.$$ + }} +}} +", $"To{shouldBeNullableQuestionMark}", @$" +public {fromClassOrStruct} From {{ + public static explicit operator To(From _) => default; +}} +public {toClassOrStruct} To {{ +}} +public class C {{ + public From{propertyNullableQuestionMark} From {{ get; }} = default; +}} +public class Program +{{ + public static void Main() + {{ + C c = null; + ((To{shouldBeNullableQuestionMark})c{conditionalAccessQuestionMark}.From)$$ + }} +}} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitConversionDescriptionIsIsGiven() + { + const string Markup = @" +public struct S { + /// + /// Explicit conversion of to . + /// + /// The to convert + public static explicit operator int(S value) => 0; +} + +public class Program +{ + public static void Main() + { + var s = new S(); + s.$$ + } +} +"; + await VerifyItemExistsAsync(Markup, "int", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +@"S.explicit operator int(S value) +Explicit conversion of S to int."); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitConversionDescriptionIsIsGivenLifted() + { + const string Markup = @" +public struct S { + /// + /// Explicit conversion of to . + /// + /// The to convert + public static explicit operator int(S value) => 0; +} + +public class Program +{ + public static void Main() + { + S? s = new S(); + s.$$ + } +} +"; + await VerifyItemExistsAsync(Markup, "int?", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +@"S.explicit operator int?(S? value) +Explicit conversion of S to int."); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInNumericConversionsAreOffered() + { + // built-in numeric conversions: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions + await VerifyCustomCommitProviderAsync(@" +public class Program +{ + public static void Main() + { + long l = 0; + l.$$ + } +} +", "int", @" +public class Program +{ + public static void Main() + { + long l = 0; + ((int)l)$$ + } +} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("sbyte", "byte", "char", "uint", "ulong", "ushort")] + [InlineData("byte", "char", "sbyte")] + [InlineData("short", "byte", "char", "sbyte", "uint", "ulong", "ushort")] + [InlineData("ushort", "byte", "char", "sbyte", "short")] + [InlineData("int", "byte", "char", "sbyte", "short", "uint", "ulong", "ushort")] + [InlineData("uint", "byte", "char", "int", "sbyte", "short", "ushort")] + [InlineData("long", "byte", "char", "int", "sbyte", "short", "uint", "ulong", "ushort")] + [InlineData("ulong", "byte", "char", "int", "long", "sbyte", "short", "uint", "ushort")] + [InlineData("char", "byte", "sbyte", "short")] + [InlineData("float", "byte", "char", "decimal", "int", "long", "sbyte", "short", "uint", "ulong", "ushort")] + [InlineData("double", "byte", "char", "decimal", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort")] + [InlineData("decimal", "byte", "char", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort")] + public async Task ExplicitBuiltInNumericConversionsAreOfferedAcordingToSpec(string fromType, params string[] toTypes) + { + // built-in numeric conversions: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions + var items = await GetCompletionItemsAsync(@$" +public class Program +{{ + public static void Main() + {{ + {fromType} i = default({fromType}); + i.$$ + }} +}} +", SourceCodeKind.Regular); + AssertEx.SetEqual(items.Select(i => i.DisplayText), toTypes); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInNumericConversionDescriptionIsLikeAConversionOperatorDescription() + { + const string Markup = @" +public class Program +{ + public static void Main() + { + int i = 0; + i.$$ + } +} +"; + await VerifyItemExistsAsync(Markup, "byte", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +$@"int.explicit operator byte(int value) +{(FormatExplicitConversionDescription(fromType: "int", toType: "byte"))}"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInNumericConversionDescriptionIsLikeAConversionOperatorDescriptionLifted() + { + const string Markup = @" +public class Program +{ + public static void Main() + { + int? i = 0; + i.$$ + } +} +"; + await VerifyItemExistsAsync(Markup, "byte?", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +$@"int.explicit operator byte?(int? value) +{(FormatExplicitConversionDescription(fromType: "int", toType: "byte"))}"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInNumericConversionsAreLifted() + { + // built-in numeric conversions: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions + await VerifyCustomCommitProviderAsync(@" +public class Program +{ + public static void Main() + { + long? l = 0; + l.$$ + } +} +", "int?", @" +public class Program +{ + public static void Main() + { + long? l = 0; + ((int?)l)$$ + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInEnumConversionsIsApplied() + { + // built-in enum conversions: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#explicit-enumeration-conversions + await VerifyCustomCommitProviderAsync(@" +public enum E { One } +public class Program +{ + public static void Main() + { + var e = E.One; + e.$$ + } +} +", "int", @" +public enum E { One } +public class Program +{ + public static void Main() + { + var e = E.One; + ((int)e)$$ + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInEnumConversionsAreLifted() + { + // built-in enum conversions: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#explicit-enumeration-conversions + await VerifyCustomCommitProviderAsync(@" +public enum E { One } +public class Program +{ + public static void Main() + { + E? e = null; + e.$$ + } +} +", "int?", @" +public enum E { One } +public class Program +{ + public static void Main() + { + E? e = null; + ((int?)e)$$ + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInEnumConversionsAreSortedAndComplete() + { + // built-in enum conversions: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#explicit-enumeration-conversions + var items = await GetCompletionItemsAsync(@" +public enum E { One } +public class Program +{ + public static void Main() + { + var e = E.One; + e.$$ + } +} +", SourceCodeKind.Regular); + var expected = new[] { "byte", "char", "decimal", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort" }; + AssertEx.SetEqual(items.Select(i => i.DisplayText), expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInEnumConversionDescriptionIsLikeAConversionOperatorDescription() + { + const string Markup = @" +public enum E { One } +public class Program +{ + public static void Main() + { + var e = E.One; + e.$$ + } +} +"; + await VerifyItemExistsAsync(Markup, "int", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +$@"E.explicit operator int(E value) +{FormatExplicitConversionDescription("E", "int")}"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInEnumConversionDescriptionIsLikeAConversionOperatorDescriptionLifted() + { + const string Markup = @" +public enum E { One } +public class Program +{ + public static void Main() + { + E? e = E.One; + e.$$ + } +} +"; + await VerifyItemExistsAsync(Markup, "int?", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +$@"E.explicit operator int?(E? value) +{(FormatExplicitConversionDescription(fromType: "E", toType: "int"))}"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitBuiltInEnumConversionDescriptionIsLikeAConversionOperatorDescriptionUnimportedNamespaceMinimalName() + { + const string Markup = @" +namespace A.B +{ + public enum E { One } +} +namespace A.C +{ + public class Program + { + public static void Main() + { + var e = B.E.One; + e.$$ + } + } +} +"; + await VerifyItemExistsAsync(Markup, "int", displayTextPrefix: "(", displayTextSuffix: ")", + glyph: (int)Glyph.Operator, + matchingFilters: new List { FilterSet.OperatorFilter }, + expectedDescriptionOrNull: +@$"B.E.explicit operator int(B.E value) +{FormatExplicitConversionDescription("B.E", "int")}"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("e.$$", true)] + [InlineData("e. $$", true)] + [InlineData("e.in$$", true)] + [InlineData("E.$$", false)] // Don't infer with enum member suggestion + [InlineData("E.One.$$", true)] + public async Task ExplicitBuiltInEnumConversionToIntAreOffered(string expression, bool conversionIsOffered) + { + Func verifyFunc = conversionIsOffered + ? markup => VerifyItemExistsAsync(markup, "int", displayTextPrefix: "(", displayTextSuffix: ")") + : markup => VerifyNoItemsExistAsync(markup); + await verifyFunc(@$" +public enum E {{ One }} +public class Program +{{ + public static void Main() + {{ + var e = E.One; + {expression} + }} +}} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task ExplicitUserDefinedConversionInheritedConversions() + { + // Base class lookup rule: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#processing-of-user-defined-explicit-conversions + await VerifyItemExistsAsync(@" +public class Base { + public static explicit operator int(Base b) => 0; +} +public class Derived: Base +{ +} +public class Program +{ + public static void Main() + { + var d = new Derived(); + var i = d.$$ + } +} +", "int", displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("C", "byte")] + [InlineData("byte", "C")] + public async Task ExplicitBuiltinConversionWithAlias(string fromType, string expected) + { + await VerifyItemExistsAsync(@$" +using C = System.Char; +public class Program +{{ + public static void Main() + {{ + var test = new {fromType}(); + var i = test.$$ + }} +}} +", expected, displayTextPrefix: "(", displayTextSuffix: ")", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateNever() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var c = new C(); + c.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class C + { + [EditorBrowsable(EditorBrowsableState.Never)] + public static explicit operator int(C _) => 0; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "int", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateNever_InheritedConversion_1() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var d = new Derived(); + d.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class Base { + [EditorBrowsable(EditorBrowsableState.Never)] + public static explicit operator int(Base b) => 0; + } + public class Derived: Base + { + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "int", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); + } + + public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateNever_InheritedConversion_2() + { + var markup = @" +namespace N +{ + public class Derived: Base + { + } + public class Program + { + public static void Main() + { + var d = new Derived(); + d.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class Base { + [EditorBrowsable(EditorBrowsableState.Never)] + public static explicit operator int(Base b) => 0; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "int", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnConversionIsRespected_EditorBrowsableStateAdvanced() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var c = new C(); + c.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class C + { + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static explicit operator int(C _) => 0; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "int", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: false); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "int", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: true); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs index 07bfd271934a8..77017fffe023a 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs @@ -33,16 +33,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, CompletionItemFlags? flags = null) + string displayTextPrefix, string inlineDescription = null, + List matchingFilters = null, CompletionItemFlags? flags = null) { await VerifyAtPositionAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, - checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags); await VerifyAtEndOfFileAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, - checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags); // Items cannot be partially written if we're checking for their absence, @@ -52,12 +53,12 @@ await VerifyAtEndOfFileAsync( await VerifyAtPosition_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + displayTextPrefix, inlineDescription, matchingFilters); await VerifyAtEndOfFile_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters); + displayTextPrefix, inlineDescription, matchingFilters); } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs new file mode 100644 index 0000000000000..9af45df20124e --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/IndexerCompletionProviderTests.cs @@ -0,0 +1,502 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders +{ + public class IndexerCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + internal override Type GetCompletionProviderType() + => typeof(UnnamedSymbolCompletionProvider); + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerIsSuggestedAfterDot() + { + await VerifyItemExistsAsync(@" +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", "this", displayTextSuffix: "[]", matchingFilters: new List { FilterSet.PropertyFilter }); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerIsSuggestedAfterDotForString() + { + await VerifyItemExistsAsync(@" +public class Program +{ + public static void Main(string s) + { + s.$$ + } +} +", "this", displayTextSuffix: "[]", matchingFilters: new List { FilterSet.PropertyFilter }); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerIsNotSuggestedOnStaticAccess() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + C.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerIsNotSuggestedInNameOfContext() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + var name = nameof(c.$$ + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerSuggestionCommitsOpenAndClosingBraces() + { + await VerifyCustomCommitProviderAsync(@" +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", "this", @" +public class C +{ + public int this[int i] => i; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c[$$] + } +} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerWithTwoParametersSuggestionCommitsOpenAndClosingBraces() + { + await VerifyCustomCommitProviderAsync(@" +public class C +{ + public int this[int x, int y] => i; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", "this", @" +public class C +{ + public int this[int x, int y] => i; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c[$$] + } +} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("c.$$", + "c[$$]")] + [InlineData("c. $$", + "c[$$] ")] + [InlineData("c.$$;", + "c[$$];")] + [InlineData("c.th$$", + "c[$$]")] + [InlineData("c.this$$", + "c[$$]")] + [InlineData("c.th$$;", + "c[$$];")] + [InlineData("var f = c.$$;", + "var f = c[$$];")] + [InlineData("var f = c.th$$;", + "var f = c[$$];")] + [InlineData("c?.$$", + "c?[$$]")] + [InlineData("c?.this$$", + "c?[$$]")] + [InlineData("((C)c).$$", + "((C)c)[$$]")] + [InlineData("(true ? c : c).$$", + "(true ? c : c)[$$]")] + public async Task IndexerCompletionForDifferentExpressions(string expression, string fixedCode) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public int this[int i] => i; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {expression} + }} +}} +", "this", @$" +public class C +{{ + public int this[int i] => i; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {fixedCode} + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("/* Leading trivia */c.$$", + "/* Leading trivia */c[$$]")] + [InlineData("c. $$ /* Trailing trivia */", + "c[$$] /* Trailing trivia */")] + [InlineData("c./* Trivia in between */$$", + "c[$$]/* Trivia in between */")] + public async Task IndexerCompletionTriviaTest(string expression, string fixedCode) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public int this[int i] => i; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {expression} + }} +}} +", "this", @$" +public class C +{{ + public int this[int i] => i; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {fixedCode} + }} +}} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerDescriptionIncludesDocCommentsAndOverloadsHint() + { + await VerifyItemExistsAsync(@" +public class C +{ + /// + /// Returns the index + /// + /// The index + /// Returns the index + public int this[int i] => i; + + /// + /// Returns 1 + /// + /// The index + /// Returns 1 + public int this[string s] => 1; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +", "this", displayTextSuffix: "[]", expectedDescriptionOrNull: @$"int C.this[int i] {{ get; }} (+ 1 {FeaturesResources.overload}) +Returns the index i"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerOfBaseTypeIsSuggestedAfterDot() + { + await VerifyItemExistsAsync(@" +public class Base +{ + public int this[int i] => i; +} +public class Derived : Base +{ +} + +public class Program +{ + public static void Main() + { + var d = new Derived(); + d.$$ + } +} +", "this", displayTextSuffix: "[]"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerOfBaseTypeIsNotSuggestedIfNotAccessible() + { + await VerifyNoItemsExistAsync(@" +public class Base +{ + protected int this[int i] => i; +} +public class Derived : Base +{ +} + +public class Program +{ + public static void Main() + { + var d = new Derived(); + d.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task IndexerIsSuggestedOnString() + { + await VerifyItemExistsAsync(@" +public class Program +{ + public static void Main() + { + var s = ""Test""; + s.$$ + } +} +", "this", displayTextSuffix: "[]"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnIndexerIsRespected_EditorBrowsableStateNever() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var c = new C(); + c.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class C + { + [EditorBrowsable(EditorBrowsableState.Never)] + public int this[int i] => i; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "this", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnIndexerIsRespected_EditorBrowsableStateAdvanced() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var c = new C(); + c.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class C + { + [EditorBrowsable(EditorBrowsableState.Advanced)] + public int this[int i] => i; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "this", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: true); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "this", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: false); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnIndexerIsRespected_EditorBrowsableStateNever_InheritedMember() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var d = new Derived(); + d.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class Base + { + [EditorBrowsable(EditorBrowsableState.Never)] + public int this[int i] => i; + } + + public class Derived: Base + { + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "this", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs index 96584f07937a0..3e42867ded91d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs @@ -32,13 +32,14 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, string inlineDescription = null, + List matchingFilters = null, CompletionItemFlags? flags = null) { return BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs new file mode 100644 index 0000000000000..12165d91daa90 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OperatorCompletionProviderTests.cs @@ -0,0 +1,836 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders +{ + public class OperatorCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + internal override Type GetCompletionProviderType() + => typeof(UnnamedSymbolCompletionProvider); + + // The suggestion is e.g. "+". If the user actually types "+" the completion list is closed. Operators therefore do not support partially written items. + protected override string? ItemPartiallyWritten(string? expectedItemOrNull) => ""; + + private static IEnumerable BinaryArithmeticAndLogicalOperators() + { + yield return new[] { "+" }; + yield return new[] { "&" }; + yield return new[] { "|" }; + yield return new[] { "/" }; + yield return new[] { "^" }; + yield return new[] { "%" }; + yield return new[] { "*" }; + yield return new[] { ">>" }; + yield return new[] { "<<" }; + yield return new[] { "-" }; + } + + private static IEnumerable BinaryEqualityAndRelationalOperators() + { + yield return new[] { "==" }; + yield return new[] { ">" }; + yield return new[] { ">=" }; + yield return new[] { "!=" }; + yield return new[] { "<" }; + yield return new[] { "<=" }; + } + + private static IEnumerable PostfixOperators() + { + yield return new[] { "++" }; + yield return new[] { "--" }; + } + + private static IEnumerable PrefixOperators() + { + yield return new[] { "!" }; + yield return new[] { "~" }; + yield return new[] { "-" }; + yield return new[] { "+" }; + } + + private static IEnumerable BinaryOperators() + => BinaryArithmeticAndLogicalOperators().Union(BinaryEqualityAndRelationalOperators()); + + private static IEnumerable UnaryOperators() + => PostfixOperators().Union(PrefixOperators()); + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorIsNotOfferedAfterNumberLiteral() + { + // User may want to type a floating point literal. + await VerifyNoItemsExistAsync(@" +public class C +{ + public static C operator +(C a, C b) => default; +} + +public class Program +{ + public static void Main() + { + 1.$$ + } +} +", SourceCodeKind.Regular); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorIsSuggestedAfterDot() + { + await VerifyItemExistsAsync(@" +public class C +{ + public static C operator +(C a, C b) => default; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$; + } +} +", "+", inlineDescription: "x + y", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("c.$$", true)] + [InlineData("c.$$;", true)] + [InlineData("c.a$$", true)] + [InlineData("c.ab$$", true)] + [InlineData("c.abc$$", true)] + [InlineData("c.abcd$$", true)] + [InlineData("c. a$$", true)] + [InlineData("c.$$a", true)] + [InlineData("c.$$ a", true)] + [InlineData("c?.$$", true)] + public async Task OperatorSuggestionOnPartiallyWrittenMember(string expression, bool isOffered) + { + var verifyAction = isOffered + ? new Func(markup => VerifyItemExistsAsync(markup, "+", inlineDescription: "x + y")) + : new Func(markup => VerifyNoItemsExistAsync(markup)); + await verifyAction(@$" +public class C +{{ + public static C operator +(C a, C b) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {expression} + }} +}} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorIsNotSuggestedOnStaticAccess() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static C operator +(C a, C b) => default; +} + +public class Program +{ + public static void Main() + { + C.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorIsNotSuggestedInNameoOfContext() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static C operator +(C a, C b) => default; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + var name = nameof(c.$$ + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorsAreSortedByImporttanceAndGroupedByTopic() + { + var items = await GetCompletionItemsAsync(@" +public class C +{ + public static C operator +(C a, C b) => null; + public static C operator -(C a, C b) => null; + public static C operator *(C a, C b) => null; + public static C operator /(C a, C b) => null; + public static C operator %(C a, C b) => null; + public static bool operator ==(C a, C b) => true; + public static bool operator !=(C a, C b) => false; + public static bool operator <(C a, C b) => true; + public static bool operator >(C a, C b) => false; + public static bool operator <=(C a, C b) => true; + public static bool operator >=(C a, C b) => false; + public static C operator +(C a) => null; + public static C operator -(C a) => null; + public static C operator ++(C a) => null; + public static C operator --(C a) => null; + public static bool operator true(C w) => true; + public static bool operator false(C w) => false; + public static bool operator &(C a, C b) => true; + public static bool operator |(C a, C b) => true; + public static C operator !(C a) => null; + public static C operator ^(C a, C b) => null; + public static C operator <<(C a, int b) => null; + public static C operator >>(C a, int b) => null; + public static C operator ~(C a) => null; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$; + } +} +", SourceCodeKind.Regular); + // true and false operators are not listed + Assert.Collection(items, + i => Assert.Equal("==", i.DisplayText), + i => Assert.Equal("!=", i.DisplayText), + i => Assert.Equal(">", i.DisplayText), + i => Assert.Equal(">=", i.DisplayText), + i => Assert.Equal("<", i.DisplayText), + i => Assert.Equal("<=", i.DisplayText), + i => Assert.Equal("!", i.DisplayText), + i => Assert.Equal("+", i.DisplayText), // Addition a+b + i => Assert.Equal("-", i.DisplayText), // Subtraction a-b + i => Assert.Equal("*", i.DisplayText), + i => Assert.Equal("/", i.DisplayText), + i => Assert.Equal("%", i.DisplayText), + i => Assert.Equal("++", i.DisplayText), + i => Assert.Equal("--", i.DisplayText), + i => Assert.Equal("+", i.DisplayText), // Unary plus +a + i => Assert.Equal("-", i.DisplayText), // Unary minus -a + i => Assert.Equal("&", i.DisplayText), + i => Assert.Equal("|", i.DisplayText), + i => Assert.Equal("^", i.DisplayText), + i => Assert.Equal("<<", i.DisplayText), + i => Assert.Equal(">>", i.DisplayText), + i => Assert.Equal("~", i.DisplayText) + ); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("bool", 0)] + [InlineData("System.Boolean", 0)] + [InlineData("char", 0)] + [InlineData("System.Char", 0)] + [InlineData("string", 0)] + [InlineData("System.String", 0)] + [InlineData("sbyte", 0)] + [InlineData("System.SByte", 0)] + [InlineData("byte", 0)] + [InlineData("System.Byte", 0)] + [InlineData("short", 0)] + [InlineData("System.Int16", 0)] + [InlineData("ushort", 0)] + [InlineData("System.UInt16", 0)] + [InlineData("int", 0)] + [InlineData("System.Int32", 0)] + [InlineData("uint", 0)] + [InlineData("System.UInt32", 0)] + [InlineData("long", 0)] + [InlineData("System.Int64", 0)] + [InlineData("ulong", 0)] + [InlineData("System.UInt64", 0)] + [InlineData("float", 0)] + [InlineData("System.Single", 0)] + [InlineData("double", 0)] + [InlineData("System.Double", 0)] + [InlineData("decimal", 0)] + [InlineData("System.Decimal", 0)] + [InlineData("nint", 0)] + [InlineData("System.IntPtr", 0)] + [InlineData("nuint", 0)] + [InlineData("System.UIntPtr", 0)] + [InlineData("System.DateTime", 8)] + [InlineData("System.TimeSpan", 10)] + [InlineData("System.DateTimeOffset", 8)] + [InlineData("System.Guid", 2)] + public async Task OperatorSuggestionForSpecialTypes(string specialType, int numberOfSuggestions) + { + var completionItems = await GetCompletionItemsAsync(@$" +public class Program +{{ + public static void Main() + {{ + {specialType} i = default({specialType}); + i.$$ + }} +}} +", SourceCodeKind.Regular); + Assert.Equal( + numberOfSuggestions, + completionItems.Count(c => c.Properties[UnnamedSymbolCompletionProvider.KindName] == UnnamedSymbolCompletionProvider.OperatorKindName)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorNoSuggestionForTrueAndFalse() + { + await VerifyNoItemsExistAsync(@" +public class C +{ + public static bool operator true(C _) => true; + public static bool operator false(C _) => true; +} + +public class Program +{ + public static void Main() + { + var c = new C(); + c.$$ + } +} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [MemberData(nameof(BinaryOperators))] + public async Task OperatorBinaryIsCompleted(string binaryOperator) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public static C operator {binaryOperator}(C a, C b) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c.$$ + }} +}} +", binaryOperator, @$" +public class C +{{ + public static C operator {binaryOperator}(C a, C b) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c {binaryOperator} $$ + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [MemberData(nameof(PostfixOperators))] + public async Task OperatorPostfixIsCompleted(string postfixOperator) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public static C operator {postfixOperator}(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c.$$ + }} +}} +", postfixOperator, @$" +public class C +{{ + public static C operator {postfixOperator}(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c{postfixOperator} $$ + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [MemberData(nameof(PrefixOperators))] + public async Task OperatorPrefixIsCompleted(string prefixOperator) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public static C operator {prefixOperator}(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c.$$ + }} +}} +", prefixOperator, @$" +public class C +{{ + public static C operator {prefixOperator}(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {prefixOperator}c$$ + }} +}} +"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorDuplicateOperatorsAreListedBoth() + { + var items = await GetCompletionItemsAsync($@" +public class C +{{ + public static C operator +(C a, C b) => default; + public static C operator +(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c.$$ + }} +}} +", SourceCodeKind.Regular); + Assert.Collection(items, + i => + { + Assert.Equal("+", i.DisplayText); + Assert.EndsWith("002_007", i.SortText); // Addition + }, + i => + { + Assert.Equal("+", i.DisplayText); + Assert.EndsWith("002_014", i.SortText); // unary plus + }); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorDuplicateOperatorsAreCompleted() + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public static C operator +(C a, C b) => default; + public static C operator +(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c.$$ + }} +}} +", "+", @$" +public class C +{{ + public static C operator +(C a, C b) => default; + public static C operator +(C _) => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + c + $$ + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [InlineData("c.$$", + "c + $$")] + [InlineData("c. $$", + "c + $$ ")] + [InlineData("c .$$", + "c + $$")] + [InlineData("c.$$;", + "c + $$;")] + [InlineData("c.abc$$", + "c + $$")] + [InlineData("c.a$$bc", + "c + $$")] + [InlineData("c.$$abc", + "c + $$abc")] + [InlineData("c.$$ abc", + "c + $$ abc")] + [InlineData("(true ? c : c).$$", + "(true ? c : c) + $$")] + [InlineData("c?.$$", + "c + $$")] + [InlineData("(true ? c : c)?.$$", + "(true ? c : c) + $$")] + [InlineData("c? .$$", + "c + $$")] + [InlineData("c ? .$$", + "c + $$")] + [InlineData("c?.CProp.$$", + "c?.CProp + $$")] + [InlineData("c?.CProp?.$$", + "c?.CProp + $$")] + [InlineData("c.CProp.CProp?.$$", + "c.CProp.CProp + $$")] + [InlineData("c?.CProp.CProp.$$", + "c?.CProp.CProp + $$")] + [InlineData("c[0].$$", + "c[0] + $$")] + [InlineData("c[0]?.$$", + "c[0] + $$")] + [InlineData("c?.CProp[0].$$", + "c?.CProp[0] + $$")] + [InlineData("c.CProp[0].CProp?.$$", + "c.CProp[0].CProp + $$")] + public async Task OperatorInfixOfferingsAndCompletions(string expression, string completion) + { + await VerifyCustomCommitProviderAsync($@" +public class C +{{ + public static C operator +(C a, C b) => default; + public C CProp {{ get; }} + public C this[int _] => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {expression} + }} +}} +", "+", @$" +public class C +{{ + public static C operator +(C a, C b) => default; + public C CProp {{ get; }} + public C this[int _] => default; +}} + +public class Program +{{ + public static void Main() + {{ + var c = new C(); + {completion} + }} +}} +"); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [MemberData(nameof(UnaryOperators))] + public async Task OperatorLiftingUnary(string operatorSign) + { + const string template = @" +public struct S +{{ + {0} => default; +}} + +public class Program +{{ + public static void Main() + {{ + S? s = null; + s.$$ + }} +}}"; + var inlineDescription = operatorSign.Length == 1 + ? $"{operatorSign}x" + : $"x{operatorSign}"; + await VerifyItemExistsAsync(string.Format(template, $"public static S operator {operatorSign}(S _)"), operatorSign, inlineDescription: inlineDescription); + await VerifyItemExistsAsync(string.Format(template, $"public static bool operator {operatorSign}(S _)"), operatorSign, inlineDescription: inlineDescription); + await VerifyNoItemsExistAsync(string.Format(template, $"public static object operator {operatorSign}(S _)")); + await VerifyNoItemsExistAsync(string.Format(template, $"public static S operator {operatorSign}(S a, S b, S c)")); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [MemberData(nameof(BinaryArithmeticAndLogicalOperators))] + public async Task OperatorLiftingBinary(string operatorSign) + { + const string template = @" +public struct S +{{ + {0} => default; +}} + +public class Program +{{ + public static void Main() + {{ + S? s = null; + s.$$ + }} +}}"; + var inlineDescription = $"x {operatorSign} y"; + await VerifyItemExistsAsync(string.Format(template, $"public static S operator {operatorSign}(S a, S b)"), operatorSign, inlineDescription: inlineDescription); + await VerifyItemExistsAsync(string.Format(template, $"public static int operator {operatorSign}(S a, S b)"), operatorSign, inlineDescription: inlineDescription); + await VerifyNoItemsExistAsync(string.Format(template, $"public static object operator {operatorSign}(S a, S b)")); + await VerifyNoItemsExistAsync(string.Format(template, $"public static S operator {operatorSign}(S a, object b)")); + await VerifyNoItemsExistAsync(string.Format(template, $"public static S operator {operatorSign}(S a, S b, S c)")); + } + + [WpfTheory, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + [MemberData(nameof(BinaryEqualityAndRelationalOperators))] + public async Task OperatorLiftingEqualityRelational(string operatorSign) + { + const string template = @" +public struct S +{{ + {0} => default; +}} + +public class Program +{{ + public static void Main() + {{ + S? s = null; + s.$$ + }} +}}"; + await VerifyItemExistsAsync(string.Format(template, $"public static bool operator {operatorSign}(S a, S b)"), operatorSign, inlineDescription: $"x { operatorSign } y"); + await VerifyNoItemsExistAsync(string.Format(template, $"public static int operator {operatorSign}(S a, S b)")); + await VerifyNoItemsExistAsync(string.Format(template, $"public static bool operator {operatorSign}(S a, S b, S c)")); + await VerifyNoItemsExistAsync(string.Format(template, $"public static bool operator {operatorSign}(S a, object b)")); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorLiftingIsApplied() + { + await VerifyCustomCommitProviderAsync(@" +public struct S +{ + public static bool operator ==(S a, S b) => default; +} + +public class Program +{ + public static void Main() + { + S? s = null; + s.$$ + } +}", "==", @" +public struct S +{ + public static bool operator ==(S a, S b) => default; +} + +public class Program +{ + public static void Main() + { + S? s = null; + s == $$ + } +}"); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorOfBaseTypeIsSuggested() + { + await VerifyItemExistsAsync(@" +public class Base { + public static int operator +(Base b, int a)=>0; +} +public class Derived: Base +{ +} + +public class Program +{ + public static void Main() + { + var d = new Derived(); + d.$$ + } +}", "+", inlineDescription: "x + y", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task OperatorForRecordsAreSuggested() + { + await VerifyItemExistsAsync(@" +public record R { +} + +public class Program +{ + public static void Main() + { + var r = new R(); + r.$$ + } +}", "==", inlineDescription: "x == y", glyph: (int)Glyph.Operator, matchingFilters: new List { FilterSet.OperatorFilter }); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnOperatorIsRespected_EditorBrowsableStateNever() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var c = new C(); + c.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class C + { + [EditorBrowsable(EditorBrowsableState.Never)] + public static C operator -(C a, C b) => default; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "-", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + [WorkItem(47511, "https://github.com/dotnet/roslyn/issues/47511")] + public async Task TestEditorBrowsableOnOperatorIsRespected_EditorBrowsableStateAdvanced() + { + var markup = @" +namespace N +{ + public class Program + { + public static void Main() + { + var c = new C(); + c.$$ + } + } +} +"; + var referencedCode = @" +using System.ComponentModel; + +namespace N +{ + public class C + { + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static C operator -(C a, C b) => default; + } +} +"; + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "-", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: false); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode, + item: "-", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: true); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs index d01e81bd00f10..0825022d2f3e3 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs @@ -36,13 +36,14 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, string inlineDescription = null, + List matchingFilters = null, CompletionItemFlags? flags = null) { return BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters, flags); + displayTextPrefix, 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 13430a9484e52..361a3072fad63 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs @@ -29,13 +29,14 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, string inlineDescription, List matchingFilters, + CompletionItemFlags? flags = null) { return base.VerifyWorkerAsync(code, position, expectedItemOrNull, expectedDescriptionOrNull, SourceCodeKind.Regular, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 884c18c246265..5348478ff3c63 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -42,17 +42,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, CompletionItemFlags? flags = null) + string displayTextPrefix, 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, + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags); await VerifyAtEndOfFileAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, - checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags); // Items cannot be partially written if we're checking for their absence, @@ -62,12 +63,12 @@ await VerifyAtEndOfFileAsync( await VerifyAtPosition_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); await VerifyAtEndOfFile_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs index 43fa06997d924..28fc50af52c43 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.Specialized; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -15,9 +14,7 @@ using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.Debugging; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Host; -using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Experiments; diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/FilterSet.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/FilterSet.cs index 00054d564a943..6f3e6abd4deb4 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/FilterSet.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/FilterSet.cs @@ -55,6 +55,7 @@ internal sealed class FilterSet public static readonly CompletionFilter PropertyFilter; public static readonly CompletionFilter MethodFilter; public static readonly CompletionFilter ExtensionMethodFilter; + public static readonly CompletionFilter OperatorFilter; public static readonly CompletionFilter LocalAndParameterFilter; public static readonly CompletionFilter KeywordFilter; public static readonly CompletionFilter SnippetFilter; @@ -82,6 +83,7 @@ static FilterSet() PropertyFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Properties, 'p', WellKnownTags.Property); MethodFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Methods, 'm', WellKnownTags.Method); ExtensionMethodFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Extension_methods, 'x', WellKnownTags.ExtensionMethod); + OperatorFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Operators, 'r', WellKnownTags.Operator); LocalAndParameterFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Locals_and_parameters, 'l', WellKnownTags.Local, WellKnownTags.Parameter); KeywordFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Keywords, 'k', WellKnownTags.Keyword); SnippetFilter = CreateCompletionFilterAndAddToBuilder(FeaturesResources.Snippets, 't', WellKnownTags.Snippet); diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs index 99c87ad546aa9..c7d8533d3beaf 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.cs @@ -236,6 +236,7 @@ internal static async Task TestAddConversionAsync( modifiers, GetTypeSymbol(toType)(context.SemanticModel), fromType(context.SemanticModel), + containingType: null, isImplicit, parsedStatements); diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 9983601aab8b1..c30665998ea7b 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -7688,6 +7688,65 @@ class C End Using End Function + + + Public Sub ConversionsOperatorsAndIndexerAreShownBelowMethodsAndPropertiesAndBeforeUnimportedItems() + Using state = TestStateFactory.CreateCSharpTestState( + +namespace A +{ + using B; + public static class CExtensions{ + public static void ExtensionUnimported(this C c) { } + } +} +namespace B +{ + public static class CExtensions{ + public static void ExtensionImported(this C c) { } + } + + public class C + { + public int A { get; } = default; + public int Z { get; } = default; + public void AM() { } + public void ZM() { } + public int this[int _] => default; + public static explicit operator int(C _) => default; + public static C operator +(C a, C b) => default; + } + + class Program + { + static void Main() + { + var c = new C(); + c.$$ + } + } +} ) + state.Workspace.SetOptions(state.Workspace.Options.WithChangedOption( + CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True)) + state.SendInvokeCompletionList() + state.AssertItemsInOrder(New String() { + "A", ' Method, properties, and imported extension methods alphabetical ordered + "AM", + "Equals", + "ExtensionImported", + "GetHashCode", + "GetType", + "this[]", ' Indexer + "ToString", + "Z", + "ZM", + "(int)", ' Conversions + "+", ' Operators + "ExtensionUnimported" 'Unimported extension methods + }) + End Using + End Sub + Public Sub TestCompleteMethodParenthesisForSymbolCompletionProvider(showCompletionInArgumentLists As Boolean, commitChar As Char) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_Conversions.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_Conversions.vb new file mode 100644 index 0000000000000..8ffee18756f0a --- /dev/null +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_Conversions.vb @@ -0,0 +1,152 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense + <[UseExportProvider]> + Public Class CSharpCompletionCommandHandlerTests_Conversions + + Public Async Function BuiltInConversion(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + state.SendTypeChars("by") + Await state.AssertSelectedCompletionItem("(byte)") + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Equal(" var y = ((byte)x)", state.GetLineTextFromCaretPosition()) + Assert.Equal(state.GetLineFromCurrentCaretPosition().End, state.GetCaretPoint().BufferPosition) + End Using + End Function + + + Public Async Function BuiltInConversion_BetweenDots(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + state.SendTypeChars("by") + Await state.AssertSelectedCompletionItem("(byte)") + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Equal(" var y = ((byte)x).ToString();", state.GetLineTextFromCaretPosition()) + Assert.Equal(".", state.GetCaretPoint().BufferPosition.GetChar()) + End Using + End Function + + + Public Async Function BuiltInConversion_PartiallyWritten_Before(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + Await state.AssertSelectedCompletionItem("CompareTo") + End Using + End Function + + + Public Async Function BuiltInConversion_PartiallyWritten_After(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + Await state.AssertSelectedCompletionItem("(byte)") + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Equal(" var y = ((byte)x).ToString();", state.GetLineTextFromCaretPosition()) + Assert.Equal(".", state.GetCaretPoint().BufferPosition.GetChar()) + End Using + End Function + + + Public Async Function BuiltInConversion_NullableType_Dot(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + state.SendTypeChars("by") + Await state.AssertSelectedCompletionItem("(byte?)") + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Equal(" var y = ((byte?)x)", state.GetLineTextFromCaretPosition()) + Assert.Equal(state.GetLineFromCurrentCaretPosition().End, state.GetCaretPoint().BufferPosition) + End Using + End Function + + + Public Async Function BuiltInConversion_NullableType_Question_BetweenDots(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + state.SendTypeChars("by") + Await state.AssertSelectedCompletionItem("(byte?)") + state.SendTab() + Await state.AssertNoCompletionSession() + Assert.Equal(" var y = ((byte?)x)?.ToString();", state.GetLineTextFromCaretPosition()) + Assert.Equal(".", state.GetCaretPoint().BufferPosition.GetChar()) + End Using + End Function + End Class +End Namespace diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index ed26942dc527c..73c8b05b72c85 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -91,7 +91,8 @@ 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, CompletionItemFlags? flags); + string displayTextPrefix, string inlineDescription, List matchingFilters, + CompletionItemFlags? flags); internal static Task GetCompletionListAsync( CompletionService service, @@ -107,7 +108,8 @@ private protected async Task CheckResultsAsync( Document document, int position, string expectedItemOrNull, string expectedDescriptionOrNull, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, - bool? hasSuggestionModeItem, string displayTextSuffix, string inlineDescription, + bool? hasSuggestionModeItem, string displayTextSuffix, + string displayTextPrefix, string inlineDescription, List matchingFilters, CompletionItemFlags? flags) { var code = (await document.GetTextAsync()).ToString(); @@ -145,6 +147,7 @@ private protected async Task CheckResultsAsync( items, c => CompareItems(c.DisplayText, expectedItemOrNull) && CompareItems(c.DisplayTextSuffix, displayTextSuffix ?? "") + && CompareItems(c.DisplayTextPrefix, displayTextPrefix ?? "") && CompareItems(c.InlineDescription, inlineDescription ?? "") && (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true)); } @@ -167,6 +170,8 @@ bool Predicate(RoslynCompletion.CompletionItem c) return false; if (!CompareItems(c.DisplayTextSuffix, displayTextSuffix ?? "")) return false; + if (!CompareItems(c.DisplayTextPrefix, displayTextPrefix ?? "")) + return false; if (!CompareItems(c.InlineDescription, inlineDescription ?? "")) return false; if (expectedDescriptionOrNull != null && completionService.GetDescriptionAsync(document, c).Result.Text != expectedDescriptionOrNull) @@ -203,7 +208,8 @@ private async 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, CompletionItemFlags? flags) + string displayTextPrefix, string inlineDescription, List matchingFilters, + CompletionItemFlags? flags) { using var workspaceFixture = GetOrCreateWorkspaceFixture(); @@ -216,8 +222,8 @@ private async Task VerifyAsync( await VerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, - matchPriority, hasSuggestionModeItem, displayTextSuffix, inlineDescription, - matchingFilters, flags).ConfigureAwait(false); + matchPriority, hasSuggestionModeItem, displayTextSuffix, displayTextPrefix, + inlineDescription, matchingFilters, flags).ConfigureAwait(false); } protected async Task GetCompletionListAsync(string markup, string workspaceKind = null) @@ -287,7 +293,7 @@ private protected async Task VerifyItemExistsAsync( string markup, string expectedItem, string expectedDescriptionOrNull = null, SourceCodeKind? sourceCodeKind = null, bool usePreviousCharAsTrigger = false, int? glyph = null, int? matchPriority = null, bool? hasSuggestionModeItem = null, - string displayTextSuffix = null, string inlineDescription = null, + string displayTextSuffix = null, string displayTextPrefix = null, string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { if (sourceCodeKind.HasValue) @@ -296,7 +302,8 @@ await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, sourceCodeKind.Value, usePreviousCharAsTrigger, checkForAbsence: false, glyph: glyph, matchPriority: matchPriority, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, - inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); + displayTextPrefix: displayTextPrefix, inlineDescription: inlineDescription, + matchingFilters: matchingFilters, flags: flags); } else { @@ -304,45 +311,77 @@ await VerifyAsync( markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Regular, usePreviousCharAsTrigger, checkForAbsence: false, glyph: glyph, matchPriority: matchPriority, hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, - inlineDescription: inlineDescription, matchingFilters: matchingFilters, flags: flags); + displayTextPrefix: displayTextPrefix, 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, flags: flags); + displayTextPrefix: displayTextPrefix, inlineDescription: inlineDescription, + matchingFilters: matchingFilters, flags: flags); } } 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, + bool? hasSuggestionModeItem = null, string displayTextSuffix = null, + string displayTextPrefix = null, string inlineDescription = 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, flags: flags); + await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, sourceCodeKind.Value, + usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, 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, 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); + await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Regular, + usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, inlineDescription: inlineDescription, + matchingFilters: matchingFilters, flags: flags); + await VerifyAsync(markup, expectedItem, expectedDescriptionOrNull, SourceCodeKind.Script, + usePreviousCharAsTrigger, checkForAbsence: true, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, inlineDescription: inlineDescription, + matchingFilters: matchingFilters, flags: flags); } } protected async Task VerifyAnyItemExistsAsync( string markup, SourceCodeKind? sourceCodeKind = null, bool usePreviousCharAsTrigger = false, - bool? hasSuggestionModeItem = null, string displayTextSuffix = null, string inlineDescription = null) + bool? hasSuggestionModeItem = null, string displayTextSuffix = null, string displayTextPrefix = null, + string inlineDescription = null) { 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, flags: null); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, + sourceCodeKind: sourceCodeKind.Value, usePreviousCharAsTrigger: usePreviousCharAsTrigger, + checkForAbsence: false, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, 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, 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); + await VerifyAsync( + markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, + sourceCodeKind: SourceCodeKind.Regular, usePreviousCharAsTrigger: usePreviousCharAsTrigger, + checkForAbsence: false, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, 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, + displayTextPrefix: displayTextPrefix, inlineDescription: inlineDescription, matchingFilters: null, + flags: null); } } @@ -353,12 +392,26 @@ 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, flags: null); + await VerifyAsync( + markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, + sourceCodeKind: sourceCodeKind.Value, usePreviousCharAsTrigger: usePreviousCharAsTrigger, + checkForAbsence: true, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: null, 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, 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); + await VerifyAsync(markup, expectedItemOrNull: null, expectedDescriptionOrNull: null, + sourceCodeKind: SourceCodeKind.Regular, usePreviousCharAsTrigger: usePreviousCharAsTrigger, + checkForAbsence: true, glyph: null, matchPriority: null, + hasSuggestionModeItem: hasSuggestionModeItem, displayTextSuffix: displayTextSuffix, + displayTextPrefix: null, 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, + displayTextPrefix: null, inlineDescription: inlineDescription, matchingFilters: null, flags: null); } } @@ -378,7 +431,7 @@ private protected virtual async Task VerifyWorkerAsync( SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionModeItem, - string displayTextSuffix, + string displayTextSuffix, string displayTextPrefix, string inlineDescription, List matchingFilters, CompletionItemFlags? flags) { @@ -391,8 +444,8 @@ await CheckResultsAsync( document1, position, expectedItemOrNull, expectedDescriptionOrNull, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, - hasSuggestionModeItem, displayTextSuffix, inlineDescription, - matchingFilters, flags); + hasSuggestionModeItem, displayTextSuffix, displayTextPrefix, + inlineDescription, matchingFilters, flags); if (await CanUseSpeculativeSemanticModelAsync(document1, position)) { @@ -400,8 +453,8 @@ await CheckResultsAsync( await CheckResultsAsync( document2, position, expectedItemOrNull, expectedDescriptionOrNull, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, - hasSuggestionModeItem, displayTextSuffix, inlineDescription, - matchingFilters, flags); + hasSuggestionModeItem, displayTextSuffix, displayTextPrefix, + inlineDescription, matchingFilters, flags); } } @@ -431,8 +484,8 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document document = WithChangedOptions(document); var service = GetCompletionService(document.Project); - var completionLlist = await GetCompletionListAsync(service, document, position, RoslynCompletion.CompletionTrigger.Invoke); - var items = completionLlist.Items; + var completionList = await GetCompletionListAsync(service, document, position, RoslynCompletion.CompletionTrigger.Invoke); + var items = completionList.Items; Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); @@ -443,7 +496,7 @@ private async Task VerifyCustomCommitProviderCheckResultsAsync(Document document } else { - await VerifyCustomCommitWorkerAsync(service, document, firstItem, completionLlist.Span, codeBeforeCommit, expectedCodeAfterCommit, commitChar); + await VerifyCustomCommitWorkerAsync(service, document, firstItem, completionList.Span, codeBeforeCommit, expectedCodeAfterCommit, commitChar); } } @@ -889,7 +942,7 @@ private protected Task VerifyAtPositionAsync( string expectedItemOrNull, string expectedDescriptionOrNull, SourceCodeKind sourceCodeKind, bool checkForAbsence, int? glyph, int? matchPriority, bool? hasSuggestionItem, - string displayTextSuffix, string inlineDescription = null, + string displayTextSuffix, string displayTextPrefix, string inlineDescription = null, List matchingFilters = null, CompletionItemFlags? flags = null) { code = code.Substring(0, position) + insertText + code.Substring(position); @@ -899,7 +952,7 @@ private protected Task VerifyAtPositionAsync( expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); } private protected Task VerifyAtPositionAsync( @@ -907,12 +960,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, CompletionItemFlags? flags = null) + string displayTextPrefix, string inlineDescription = null, + List matchingFilters = null, CompletionItemFlags? flags = null) { return VerifyAtPositionAsync( code, position, string.Empty, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, - glyph, matchPriority, hasSuggestionItem, displayTextSuffix, + glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags); } @@ -921,7 +975,8 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, 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) @@ -935,8 +990,8 @@ private protected async Task VerifyAtEndOfFileAsync( await BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, - matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, - matchingFilters, flags); + matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, + inlineDescription, matchingFilters, flags); } private protected Task VerifyAtPosition_ItemPartiallyWrittenAsync( @@ -944,13 +999,14 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, 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, flags); + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, + displayTextPrefix, inlineDescription, matchingFilters, flags); } private protected Task VerifyAtEndOfFileAsync( @@ -958,12 +1014,13 @@ 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, CompletionItemFlags? flags = null) + string displayTextPrefix, 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, flags); + displayTextPrefix, inlineDescription, matchingFilters, flags); } private protected Task VerifyAtEndOfFile_ItemPartiallyWrittenAsync( @@ -971,12 +1028,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, CompletionItemFlags? flags = null) + string displayTextPrefix, 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, + glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags); } diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb index 3ce052ebb3c02..42721cc4584c3 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestState.vb @@ -378,15 +378,15 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense End Sub Public Async Function AssertSelectedCompletionItem( - Optional displayText As String = Nothing, - Optional displayTextSuffix As String = Nothing, - Optional description As String = Nothing, - Optional isSoftSelected As Boolean? = Nothing, - Optional isHardSelected As Boolean? = Nothing, - Optional shouldFormatOnCommit As Boolean? = Nothing, - Optional inlineDescription As String = Nothing, - Optional automationText As String = Nothing, - Optional projectionsView As ITextView = Nothing) As Task + Optional displayText As String = Nothing, + Optional displayTextSuffix As String = Nothing, + Optional description As String = Nothing, + Optional isSoftSelected As Boolean? = Nothing, + Optional isHardSelected As Boolean? = Nothing, + Optional shouldFormatOnCommit As Boolean? = Nothing, + Optional inlineDescription As String = Nothing, + Optional automationText As String = Nothing, + Optional projectionsView As ITextView = Nothing) As Task Await WaitForAsynchronousOperationsAsync() Dim view = If(projectionsView, TextView) diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb index bbd5d3fdce93d..6c54a27301016 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AbstractVisualBasicCompletionProviderTests.vb @@ -28,12 +28,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, - hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, + hasSuggestionItem As Boolean?, displayTextSuffix As String, displayTextPrefix As String, inlineDescription As String, 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, + glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags) End Function @@ -42,7 +42,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, - hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, + hasSuggestionItem As Boolean?, displayTextSuffix As String, displayTextPrefix As String, inlineDescription As String, 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. @@ -52,12 +52,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Await VerifyAtPositionAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, - checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags) Await VerifyAtEndOfFileAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, - checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, + checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags) ' Items cannot be partially written if we're checking for their absence, @@ -65,13 +65,13 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet If Not checkForAbsence AndAlso expectedItemOrNull <> Nothing Then Await VerifyAtPosition_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, - sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters) + sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, + displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters) Await VerifyAtEndOfFile_ItemPartiallyWrittenAsync( code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, - sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, - inlineDescription, matchingFilters) + sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, + displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters) End If End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb index 98b9a759a5bde..072b496c40acd 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ObjectInitializerCompletionProviderTests.vb @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, - hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, + hasSuggestionItem As Boolean?, displayTextSuffix As String, displayTextPrefix As String, inlineDescription As String, 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. @@ -28,7 +28,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Await BaseVerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, usePreviousCharAsTrigger, checkForAbsence, glyph, - matchPriority, hasSuggestionItem, displayTextSuffix, inlineDescription, + matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags) End Function diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb index de250229966f6..76dc8c703a11b 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/OverrideCompletionProviderTests.vb @@ -564,7 +564,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, Nothing) + Await BaseVerifyWorkerAsync(code, position, "[Class]()", "Sub CBase.Class()", SourceCodeKind.Regular, False, False, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) End Function @@ -585,7 +585,7 @@ End Class.Value Await BaseVerifyWorkerAsync( code, position, "[Class]", "Property CBase.Class As Integer", - SourceCodeKind.Regular, False, False, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) + SourceCodeKind.Regular, False, False, Nothing, 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 fb943e628c166..ad573e1311093 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/SymbolCompletionProviderTests.vb @@ -1352,7 +1352,7 @@ Class C expectedItemOrNull:="10", expectedDescriptionOrNull:=Nothing, sourceCodeKind:=SourceCodeKind.Regular, checkForAbsence:=False, glyph:=Nothing, matchPriority:=Nothing, hasSuggestionItem:=Nothing, - displayTextSuffix:=Nothing, matchingFilters:=Nothing) + displayTextSuffix:=Nothing, displayTextPrefix:=Nothing, matchingFilters:=Nothing) End Function @@ -7814,10 +7814,10 @@ End Namespace Dim document = workspace.CurrentSolution.GetDocument(workspace.DocumentWithCursor.Id) 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, + glyph:=Nothing, matchPriority:=Nothing, hasSuggestionModeItem:=Nothing, displayTextSuffix:=Nothing, displayTextPrefix:=Nothing, inlineDescription:=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, + glyph:=Nothing, matchPriority:=Nothing, hasSuggestionModeItem:=Nothing, displayTextSuffix:=Nothing, displayTextPrefix:=Nothing, inlineDescription:=Nothing, matchingFilters:=Nothing, flags:=Nothing) End Using diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb index 95bfdce753e8b..4926a0a87ae48 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/XmlDocCommentCompletionProviderTests.vb @@ -21,10 +21,10 @@ Namespace Tests expectedItemOrNull As String, expectedDescriptionOrNull As String, sourceCodeKind As SourceCodeKind, usePreviousCharAsTrigger As Boolean, checkForAbsence As Boolean, glyph As Integer?, matchPriority As Integer?, - hasSuggestionItem As Boolean?, displayTextSuffix As String, inlineDescription As String, + hasSuggestionItem As Boolean?, displayTextSuffix As String, displayTextPrefix As String, inlineDescription As String, 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) + Await VerifyAtPositionAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags) + Await VerifyAtEndOfFileAsync(code, position, usePreviousCharAsTrigger, expectedItemOrNull, expectedDescriptionOrNull, sourceCodeKind, checkForAbsence, glyph, matchPriority, hasSuggestionItem, displayTextSuffix, displayTextPrefix, inlineDescription, matchingFilters, flags) End Function Private Async Function VerifyItemsExistAsync(markup As String, ParamArray items() As String) As Task diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs index 0528bce91d466..890e9f47abdef 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs @@ -29,6 +29,18 @@ public static bool IsWordCharacter(char ch) public static bool IsCompletionItemStartCharacter(char ch) => ch == '@' || IsWordCharacter(ch); + public static bool TreatAsDot(SyntaxToken token, int characterPosition) + { + if (token.Kind() == SyntaxKind.DotToken) + return true; + + // if we're right after the first dot in .. then that's considered completion on dot. + if (token.Kind() == SyntaxKind.DotDotToken && token.SpanStart == characterPosition) + return true; + + return false; + } + internal static bool IsTriggerCharacter(SourceText text, int characterPosition, OptionSet options) { var ch = text[characterPosition]; diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs index 8e001e6a1a21a..7f43622428a77 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceMemberCompletionProvider.cs @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { [ExportCompletionProvider(nameof(ExplicitInterfaceMemberCompletionProvider), LanguageNames.CSharp), Shared] - [ExtensionOrder(After = nameof(SymbolCompletionProvider))] + [ExtensionOrder(After = nameof(UnnamedSymbolCompletionProvider))] internal partial class ExplicitInterfaceMemberCompletionProvider : LSPCompletionProvider { private static readonly SymbolDisplayFormat s_signatureDisplayFormat = diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs new file mode 100644 index 0000000000000..5a7ba76f5e621 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Recommendations; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers +{ + /// + /// Provides completion for uncommon unnamed symbols, like conversions, indexer and operators. These completion + /// items will be brought up with dot like normal, but will end up inserting more than just a name into + /// the editor. For example, committing a conversion will insert the conversion prior to the expression being + /// dotted off of. + /// + [ExportCompletionProvider(nameof(UnnamedSymbolCompletionProvider), LanguageNames.CSharp), Shared] + [ExtensionOrder(After = nameof(SymbolCompletionProvider))] + internal partial class UnnamedSymbolCompletionProvider : LSPCompletionProvider + { + /// + /// CompletionItems for indexers/operators should be sorted below other suggestions like methods or properties + /// of the type. We accomplish this by placing a character known to be greater than all other normal identifier + /// characters as the start of our item's name. This doesn't affect what we insert though as all derived + /// providers have specialized logic for what they need to do. + /// + private const string SortingPrefix = "\uFFFD"; + + /// + /// Used to store what sort of unnamed symbol a completion item represents. + /// + internal const string KindName = "Kind"; + internal const string IndexerKindName = "Indexer"; + internal const string OperatorKindName = "Operator"; + internal const string ConversionKindName = "Conversion"; + + /// + /// Used to store the doc comment for some operators/conversions. This is because some of them will be + /// synthesized, so there will be no symbol we can recover after the fact in . + /// + private const string DocumentationCommentXmlName = "DocumentationCommentXml"; + + [ImportingConstructor] + [System.Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public UnnamedSymbolCompletionProvider() + { + } + + public override ImmutableHashSet TriggerCharacters => ImmutableHashSet.Create('.'); + + public override bool IsInsertionTrigger(SourceText text, int insertedCharacterPosition, OptionSet options) + => text[insertedCharacterPosition] == '.'; + + /// + /// We keep operators sorted in a specific order. We don't want to sort them alphabetically, but instead want + /// to keep things like == and != together. + /// + private static string SortText(int sortingGroupIndex, string sortTextSymbolPart) + => $"{SortingPrefix}{sortingGroupIndex:000}_{sortTextSymbolPart}"; + + /// + /// Gets the dot-like token we're after, and also the start of the expression we'd want to place any text before. + /// + private static (SyntaxToken dotLikeToken, int expressionStart) GetDotAndExpressionStart(SyntaxNode root, int position) + { + var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true); + var dotToken = tokenOnLeft.GetPreviousTokenIfTouchingWord(position); + + // Has to be a . or a .. token + if (!CompletionUtilities.TreatAsDot(dotToken, position - 1)) + return default; + + // don't want to trigger after a number. All other cases after dot are ok. + if (dotToken.GetPreviousToken().Kind() == SyntaxKind.NumericLiteralToken) + return default; + + // if we have `.Name`, we want to get the parent member-access of that to find the starting position. + // Otherwise, if we have .. then we want the left side of that to find the starting position. + var expression = dotToken.Kind() == SyntaxKind.DotToken + ? dotToken.Parent as ExpressionSyntax + : (dotToken.Parent as RangeExpressionSyntax)?.LeftOperand; + + if (expression == null) + return default; + + // If we're after a ?. find the root of that conditional to find the start position of the expression. + expression = expression.GetRootConditionalAccessExpression() ?? expression; + return (dotToken, expression.SpanStart); + } + + public override async Task ProvideCompletionsAsync(CompletionContext context) + { + var cancellationToken = context.CancellationToken; + var document = context.Document; + var position = context.Position; + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var dotAndExprStart = GetDotAndExpressionStart(root, position); + if (dotAndExprStart == default) + return; + + var recommender = document.GetRequiredLanguageService(); + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var options = CodeAnalysis.Completion.Providers.CompletionUtilities.GetUpdatedRecommendationOptions(context.Options, document.Project.Language); + var recommendedSymbols = recommender.GetRecommendedSymbolsAtPosition( + document.Project.Solution.Workspace, semanticModel, position, options, cancellationToken); + + AddUnnamedSymbols(context, position, semanticModel, recommendedSymbols.UnnamedSymbols, cancellationToken); + } + + private void AddUnnamedSymbols( + CompletionContext context, int position, SemanticModel semanticModel, ImmutableArray unnamedSymbols, CancellationToken cancellationToken) + { + // Add one 'this[]' entry for all the indexers this type may have. + AddIndexers(context, unnamedSymbols.WhereAsArray(s => s.IsIndexer())); + + // Group all the related operators and add a single completion entry per group. + var operatorGroups = unnamedSymbols.WhereAsArray(s => s.IsUserDefinedOperator()).GroupBy(op => op.Name); + foreach (var opGroup in operatorGroups) + AddOperatorGroup(context, opGroup.Key, opGroup); + + foreach (var symbol in unnamedSymbols) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (symbol.IsConversion()) + AddConversion(context, semanticModel, position, (IMethodSymbol)symbol); + } + } + + internal override Task GetChangeAsync( + Document document, + CompletionItem item, + TextSpan completionListSpan, + char? commitKey, + bool disallowAddingImports, + CancellationToken cancellationToken) + { + var kind = item.Properties[KindName]; + return kind switch + { + IndexerKindName => GetIndexerChangeAsync(document, item, cancellationToken), + OperatorKindName => GetOperatorChangeAsync(document, item, cancellationToken), + ConversionKindName => GetConversionChangeAsync(document, item, cancellationToken), + _ => throw ExceptionUtilities.UnexpectedValue(kind), + }; + } + + public override async Task GetDescriptionAsync( + Document document, + CompletionItem item, + CancellationToken cancellationToken) + { + var kind = item.Properties[KindName]; + return kind switch + { + IndexerKindName => await GetIndexerDescriptionAsync(document, item, cancellationToken).ConfigureAwait(false), + OperatorKindName => await GetOperatorDescriptionAsync(document, item, cancellationToken).ConfigureAwait(false), + ConversionKindName => await GetConversionDescriptionAsync(document, item, cancellationToken).ConfigureAwait(false), + _ => throw ExceptionUtilities.UnexpectedValue(kind), + }; + } + + private static Task ReplaceTextAfterOperatorAsync(Document document, CompletionItem item, string text, CancellationToken cancellationToken) + => ReplaceTextAfterOperatorAsync(document, item, text, keepQuestion: false, positionOffset: 0, cancellationToken); + + private static async Task ReplaceTextAfterOperatorAsync( + Document document, + CompletionItem item, + string text, + bool keepQuestion, + int positionOffset, + CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var position = SymbolCompletionItem.GetContextPosition(item); + + var (dotToken, _) = GetDotAndExpressionStart(root, position); + var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken + ? dotToken.GetPreviousToken() + : (SyntaxToken?)null; + + var replacementStart = !keepQuestion && questionToken != null + ? questionToken.Value.SpanStart + : dotToken.SpanStart; + var newPosition = replacementStart + text.Length + positionOffset; + + var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true); + return CompletionChange.Create( + new TextChange(TextSpan.FromBounds(replacementStart, tokenOnLeft.Span.End), text), + newPosition); + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs new file mode 100644 index 0000000000000..05fbf8f21c2f8 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers +{ + internal partial class UnnamedSymbolCompletionProvider + { + // Place conversions before operators. + private readonly int ConversionSortingGroupIndex = 1; + + /// + /// Tag to let us know we need to rehydrate the conversion from the parameter and return type. + /// + private const string RehydrateName = "Rehydrate"; + private static readonly ImmutableDictionary ConversionProperties = + ImmutableDictionary.Empty.Add(KindName, ConversionKindName); + + private void AddConversion(CompletionContext context, SemanticModel semanticModel, int position, IMethodSymbol conversion) + { + var (symbols, properties) = GetConversionSymbolsAndProperties(context, conversion); + + var targetTypeName = conversion.ReturnType.ToMinimalDisplayString(semanticModel, position); + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayTextPrefix: "(", + displayText: targetTypeName, + displayTextSuffix: ")", + filterText: targetTypeName, + sortText: SortText(ConversionSortingGroupIndex, targetTypeName), + glyph: Glyph.Operator, + symbols: symbols, + rules: CompletionItemRules.Default, + contextPosition: position, + properties: properties)); + } + + private static (ImmutableArray symbols, ImmutableDictionary properties) GetConversionSymbolsAndProperties( + CompletionContext context, IMethodSymbol conversion) + { + // If it's a non-synthesized method, then we can just encode it as is. + if (conversion is not CodeGenerationSymbol) + return (ImmutableArray.Create(conversion), ConversionProperties); + + // Otherwise, encode the constituent parts so we can recover it in GetConversionDescriptionAsync; + var properties = ConversionProperties + .Add(RehydrateName, RehydrateName) + .Add(DocumentationCommentXmlName, conversion.GetDocumentationCommentXml(cancellationToken: context.CancellationToken) ?? ""); + var symbols = ImmutableArray.Create(conversion.ContainingType, conversion.Parameters.First().Type, conversion.ReturnType); + return (symbols, properties); + } + + private static async Task GetConversionChangeAsync( + Document document, CompletionItem item, CancellationToken cancellationToken) + { + var position = SymbolCompletionItem.GetContextPosition(item); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var (dotToken, _) = GetDotAndExpressionStart(root, position); + + var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken + ? dotToken.GetPreviousToken() + : (SyntaxToken?)null; + + var expression = (ExpressionSyntax)dotToken.GetRequiredParent(); + expression = expression.GetRootConditionalAccessExpression() ?? expression; + + var replacement = questionToken != null + ? $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, questionToken.Value.FullSpan.Start))}){questionToken.Value}" + : $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, dotToken.SpanStart))})"; + + // If we're at `x.$$.y` then we only want to replace up through the first dot. + var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true); + var fullTextChange = new TextChange( + TextSpan.FromBounds( + expression.SpanStart, + tokenOnLeft.Kind() == SyntaxKind.DotDotToken ? tokenOnLeft.SpanStart + 1 : tokenOnLeft.Span.End), + replacement); + + var newPosition = expression.SpanStart + replacement.Length; + return CompletionChange.Create(fullTextChange, newPosition); + } + + private static async Task GetConversionDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + { + var conversion = await TryRehydrateAsync(document, item, cancellationToken).ConfigureAwait(false); + if (conversion == null) + return null; + + return await SymbolCompletionItem.GetDescriptionForSymbolsAsync( + item, document, ImmutableArray.Create(conversion), cancellationToken).ConfigureAwait(false); + } + + private static async Task TryRehydrateAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + { + // If we're need to rehydrate the conversion, pull out the necessary parts. + if (item.Properties.ContainsKey(RehydrateName)) + { + var symbols = await SymbolCompletionItem.GetSymbolsAsync(item, document, cancellationToken).ConfigureAwait(false); + if (symbols.Length == 3 && + symbols[0] is INamedTypeSymbol containingType && + symbols[1] is ITypeSymbol fromType && + symbols[2] is ITypeSymbol toType) + { + return CodeGenerationSymbolFactory.CreateConversionSymbol( + toType: toType, + fromType: CodeGenerationSymbolFactory.CreateParameterSymbol(fromType, "value"), + containingType: containingType, + documentationCommentXml: item.Properties[DocumentationCommentXmlName]); + } + + return null; + } + else + { + // Otherwise, just go retrieve the conversion directly. + var symbols = await SymbolCompletionItem.GetSymbolsAsync(item, document, cancellationToken).ConfigureAwait(false); + return symbols.Length == 1 ? symbols.Single() : null; + } + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs new file mode 100644 index 0000000000000..be991e8586564 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Indexers.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers +{ + internal partial class UnnamedSymbolCompletionProvider + { + private readonly ImmutableDictionary IndexerProperties = + ImmutableDictionary.Empty.Add(KindName, IndexerKindName); + + private void AddIndexers(CompletionContext context, ImmutableArray indexers) + { + if (indexers.Length == 0) + return; + + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText: "this", + displayTextSuffix: "[]", + filterText: "this", + sortText: "this", + symbols: indexers, + rules: CompletionItemRules.Default, + contextPosition: context.Position, + properties: IndexerProperties)); + } + + // Remove the dot, but leave the ? if one is there. Place the caret one space back so it is between the braces. + private static Task GetIndexerChangeAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + => ReplaceTextAfterOperatorAsync(document, item, text: "[]", keepQuestion: true, positionOffset: -1, cancellationToken); + + private static Task GetIndexerDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + => SymbolCompletionItem.GetDescriptionAsync(item, document, cancellationToken); + } +} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs new file mode 100644 index 0000000000000..cfaf1bdb7376e --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers +{ + internal partial class UnnamedSymbolCompletionProvider + { + [Flags] + private enum OperatorPosition + { + None = 0, + Prefix = 1, + Infix = 2, + Postfix = 4, + } + + // Place operators after conversions. + private readonly int OperatorSortingGroupIndex = 2; + + private readonly string OperatorName = nameof(OperatorName); + private readonly ImmutableDictionary OperatorProperties = + ImmutableDictionary.Empty.Add(KindName, OperatorKindName); + + /// + /// Ordered in the order we want to display operators in the completion list. + /// + private static readonly ImmutableArray<(string name, OperatorPosition position)> s_operatorInfo1 = + ImmutableArray.Create( + (WellKnownMemberNames.EqualityOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.InequalityOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.GreaterThanOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.GreaterThanOrEqualOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.LessThanOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.LessThanOrEqualOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.LogicalNotOperatorName, OperatorPosition.Prefix), + (WellKnownMemberNames.AdditionOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.SubtractionOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.MultiplyOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.DivisionOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.ModulusOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.IncrementOperatorName, OperatorPosition.Prefix | OperatorPosition.Postfix), + (WellKnownMemberNames.DecrementOperatorName, OperatorPosition.Prefix | OperatorPosition.Postfix), + (WellKnownMemberNames.UnaryPlusOperatorName, OperatorPosition.Prefix), + (WellKnownMemberNames.UnaryNegationOperatorName, OperatorPosition.Prefix), + (WellKnownMemberNames.BitwiseAndOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.BitwiseOrOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.ExclusiveOrOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.LeftShiftOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.RightShiftOperatorName, OperatorPosition.Infix), + (WellKnownMemberNames.OnesComplementOperatorName, OperatorPosition.Prefix)); + + /// + /// Mapping from operator name to info about it. + /// + private static readonly Dictionary s_operatorNameToInfo = new(); + + private static readonly CompletionItemRules s_operatorRules; + + static UnnamedSymbolCompletionProvider() + { + // Collect all the characters used in C# operators and make them filter characters and not commit + // characters. We want people to be able to write `x.=` and have that filter down to operators like `==` and + // `!=` so they can select and commit them. + using var _ = PooledHashSet.GetInstance(out var filterCharacters); + + for (var i = 0; i < s_operatorInfo1.Length; i++) + { + var (opName, position) = s_operatorInfo1[i]; + var opText = GetOperatorText(opName); + s_operatorNameToInfo[opName] = (sortOrder: i, position); + + foreach (var ch in opText) + { + if (!char.IsLetterOrDigit(ch)) + filterCharacters.Add(ch); + } + } + + var opCharacters = ImmutableArray.CreateRange(filterCharacters); + s_operatorRules = CompletionItemRules.Default + .WithFilterCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Add, opCharacters)) + .WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, opCharacters)); + } + + private void AddOperatorGroup(CompletionContext context, string opName, IEnumerable operators) + { + if (!s_operatorNameToInfo.TryGetValue(opName, out var sortOrderAndPosition)) + return; + + var displayText = GetOperatorText(opName); + + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText: displayText, + displayTextSuffix: null, + inlineDescription: GetOperatorInlineDescription(opName), + filterText: displayText, + sortText: SortText(OperatorSortingGroupIndex, $"{sortOrderAndPosition.sortOrder:000}"), + symbols: operators.ToImmutableArray(), + rules: s_operatorRules, + contextPosition: context.Position, + properties: OperatorProperties.Add(OperatorName, opName))); + } + + private static string GetOperatorText(string opName) + => SyntaxFacts.GetText(SyntaxFacts.GetOperatorKind(opName)); + + private async Task GetOperatorChangeAsync( + Document document, CompletionItem item, CancellationToken cancellationToken) + { + var opName = item.Properties[OperatorName]; + var opPosition = GetOperatorPosition(opName); + + if (opPosition.HasFlag(OperatorPosition.Infix)) + return await ReplaceTextAfterOperatorAsync(document, item, text: $" {item.DisplayText} ", cancellationToken).ConfigureAwait(false); + + if (opPosition.HasFlag(OperatorPosition.Postfix)) + return await ReplaceTextAfterOperatorAsync(document, item, text: $"{item.DisplayText} ", cancellationToken).ConfigureAwait(false); + + if (opPosition.HasFlag(OperatorPosition.Prefix)) + { + var position = SymbolCompletionItem.GetContextPosition(item); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var (dotLikeToken, expressionStart) = GetDotAndExpressionStart(root, position); + + // Place the new operator before the expression, and delete the dot. + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var replacement = item.DisplayText + text.ToString(TextSpan.FromBounds(expressionStart, dotLikeToken.SpanStart)); + var fullTextChange = new TextChange( + TextSpan.FromBounds( + expressionStart, + dotLikeToken.Kind() == SyntaxKind.DotDotToken ? dotLikeToken.Span.Start + 1 : dotLikeToken.Span.End), + replacement); + + var newPosition = expressionStart + replacement.Length; + return CompletionChange.Create(fullTextChange, newPosition); + } + + throw ExceptionUtilities.UnexpectedValue(opPosition); + } + + private static OperatorPosition GetOperatorPosition(string operatorName) + => s_operatorNameToInfo[operatorName].position; + + private static Task GetOperatorDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + => SymbolCompletionItem.GetDescriptionAsync(item, document, cancellationToken); + + private static string GetOperatorInlineDescription(string opName) + { + var opText = GetOperatorText(opName); + var opPosition = GetOperatorPosition(opName); + + if (opPosition.HasFlag(OperatorPosition.Postfix)) + return $"x{opText}"; + + if (opPosition.HasFlag(OperatorPosition.Infix)) + return $"x {opText} y"; + + if (opPosition.HasFlag(OperatorPosition.Prefix)) + return $"{opText}x"; + + return opText; + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs index 7e2872cb1ec5c..2f62a02807e29 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/SymbolCompletionProvider.cs @@ -134,25 +134,13 @@ private static bool ShouldTriggerInArgumentLists(OptionSet options) protected override bool IsTriggerOnDot(SyntaxToken token, int characterPosition) { - if (!IsDot(token, characterPosition)) + if (!CompletionUtilities.TreatAsDot(token, characterPosition)) return false; // don't want to trigger after a number. All other cases after dot are ok. return token.GetPreviousToken().Kind() != SyntaxKind.NumericLiteralToken; } - private static bool IsDot(SyntaxToken token, int characterPosition) - { - if (token.Kind() == SyntaxKind.DotToken) - return true; - - // if we're right after the first dot in .. then that's considered completion on dot. - if (token.Kind() == SyntaxKind.DotDotToken && token.SpanStart == characterPosition) - return true; - - return false; - } - /// if not an argument list character, otherwise whether the trigger is in an argument list. private static async Task IsTriggerInArgumentListAsync(Document document, int characterPosition, CancellationToken cancellationToken) { diff --git a/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs b/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs index d9406b282f10b..a96163df9589d 100644 --- a/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs +++ b/src/Features/CSharp/Portable/LanguageServices/CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs @@ -101,6 +101,12 @@ protected override Task> GetInitializerSourceP return SpecializedTasks.EmptyImmutableArray(); } + protected override ImmutableArray ToMinimalDisplayParts(ISymbol symbol, SemanticModel semanticModel, int position, SymbolDisplayFormat format) + => CodeAnalysis.CSharp.SymbolDisplay.ToMinimalDisplayParts(symbol, semanticModel, position, format); + + protected override string GetNavigationHint(ISymbol symbol) + => symbol == null ? null : CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(symbol, SymbolDisplayFormat.MinimallyQualifiedFormat); + private async Task> GetInitializerSourcePartsAsync( IFieldSymbol symbol) { diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index eb4f3fe966363..42cbd973b5cf9 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -897,9 +897,7 @@ private async Task GenerateInferredCallsiteExpressionAsync( } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - - var recommendations = await Recommender.GetImmutableRecommendedSymbolsAtPositionAsync( - semanticModel, position, document.Project.Solution.Workspace, cancellationToken: cancellationToken).ConfigureAwait(false); + var recommendations = Recommender.GetRecommendedSymbolsAtPosition(semanticModel, position, document.Project.Solution.Workspace, options: null, cancellationToken); var sourceSymbols = recommendations.Where(r => r.IsNonImplicitAndFromSource()); diff --git a/src/Features/Core/Portable/Common/TaggedText.cs b/src/Features/Core/Portable/Common/TaggedText.cs index 988a54176dc2a..dba173e80f714 100644 --- a/src/Features/Core/Portable/Common/TaggedText.cs +++ b/src/Features/Core/Portable/Common/TaggedText.cs @@ -87,15 +87,16 @@ public override string ToString() internal static class TaggedTextExtensions { - public static ImmutableArray ToTaggedText(this IEnumerable displayParts) - => displayParts.ToTaggedText(TaggedTextStyle.None); + public static ImmutableArray ToTaggedText(this IEnumerable displayParts, Func getNavigationHint = null) + => displayParts.ToTaggedText(TaggedTextStyle.None, getNavigationHint); - public static ImmutableArray ToTaggedText(this IEnumerable displayParts, TaggedTextStyle style) + public static ImmutableArray ToTaggedText( + this IEnumerable displayParts, TaggedTextStyle style, Func getNavigationHint = null) { if (displayParts == null) - { return ImmutableArray.Empty; - } + + getNavigationHint ??= GetNavigationHint; return displayParts.SelectAsArray(d => new TaggedText( @@ -103,7 +104,7 @@ public static ImmutableArray ToTaggedText(this IEnumerable symbol?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); } + private static string GetNavigationHint(ISymbol symbol) + => symbol?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + public static string JoinText(this ImmutableArray values) { diff --git a/src/Features/Core/Portable/Completion/CommonCompletionItem.cs b/src/Features/Core/Portable/Completion/CommonCompletionItem.cs index f3b13482d4f55..485dcd46d2769 100644 --- a/src/Features/Core/Portable/Completion/CommonCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/CommonCompletionItem.cs @@ -26,6 +26,7 @@ public static CompletionItem Create( ImmutableDictionary properties = null, ImmutableArray tags = default, string inlineDescription = null, + string displayTextPrefix = null, bool isComplexTextEdit = false) { tags = tags.NullToEmpty(); @@ -50,6 +51,7 @@ public static CompletionItem Create( return CompletionItem.Create( displayText: displayText, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, filterText: filterText, sortText: sortText, properties: properties, diff --git a/src/Features/Core/Portable/Completion/CommonCompletionUtilities.cs b/src/Features/Core/Portable/Completion/CommonCompletionUtilities.cs index 71878b43263ae..cb4f0465d32a9 100644 --- a/src/Features/Core/Portable/Completion/CommonCompletionUtilities.cs +++ b/src/Features/Core/Portable/Completion/CommonCompletionUtilities.cs @@ -123,6 +123,7 @@ public static async Task CreateDescriptionAsync( switch (symbol.Kind) { case SymbolKind.Method: + case SymbolKind.Property: case SymbolKind.NamedType: if (overloadCount > 0) { diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs index 206a6f4b528ef..8e65403611734 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs @@ -32,17 +32,17 @@ internal abstract class AbstractRecommendationServiceBasedCompletionProvider(); - var recommendedSymbols = await recommender.GetRecommendedSymbolsAtPositionAsync(context.Workspace, context.SemanticModel, position, options, cancellationToken).ConfigureAwait(false); + var recommendedSymbols = recommender.GetRecommendedSymbolsAtPosition(context.Workspace, context.SemanticModel, position, options, cancellationToken); var shouldPreselectInferredTypes = await ShouldPreselectInferredTypesAsync(completionContext, position, options, cancellationToken).ConfigureAwait(false); if (!shouldPreselectInferredTypes) - return recommendedSymbols.SelectAsArray(s => (s, preselect: false)); + return recommendedSymbols.NamedSymbols.SelectAsArray(s => (s, preselect: false)); var inferredTypes = context.InferredTypes.Where(t => t.SpecialType != SpecialType.System_Void).ToSet(); using var _ = ArrayBuilder<(ISymbol symbol, bool preselect)>.GetInstance(out var result); - foreach (var symbol in recommendedSymbols) + foreach (var symbol in recommendedSymbols.NamedSymbols) { // Don't preselect intrinsic type symbols so we can preselect their keywords instead. We will also // ignore nullability for purposes of preselection -- if a method is returning a string? but we've diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index bb9972be247e4..1b49dbd032445 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs @@ -298,7 +298,7 @@ private async Task> GetItemsAsync( CancellationToken cancellationToken) { var relatedDocumentIds = document.GetLinkedDocumentIds(); - options = GetUpdatedRecommendationOptions(options, document.Project.Language); + options = CompletionUtilities.GetUpdatedRecommendationOptions(options, document.Project.Language); if (relatedDocumentIds.IsEmpty) { @@ -386,16 +386,6 @@ protected virtual Task IsSemanticTriggerCharacterAsync(Document document, : await GetSymbolsAsync(completionContext, syntaxContext, syntaxContext.Position, options, cancellationToken).ConfigureAwait(false); } - protected static OptionSet GetUpdatedRecommendationOptions(OptionSet options, string language) - { - var filterOutOfScopeLocals = options.GetOption(CompletionControllerOptions.FilterOutOfScopeLocals); - var hideAdvancedMembers = options.GetOption(CompletionOptions.HideAdvancedMembers, language); - - return options - .WithChangedOption(RecommendationOptions.FilterOutOfScopeLocals, language, filterOutOfScopeLocals) - .WithChangedOption(RecommendationOptions.HideAdvancedMembers, language, hideAdvancedMembers); - } - protected static async Task CreateContextAsync(Document document, int position, CancellationToken cancellationToken) { var workspace = document.Project.Solution.Workspace; diff --git a/src/Features/Core/Portable/Completion/Providers/CompletionUtilities.cs b/src/Features/Core/Portable/Completion/Providers/CompletionUtilities.cs index ec3a0e0b626a0..81d428bf9b615 100644 --- a/src/Features/Core/Portable/Completion/Providers/CompletionUtilities.cs +++ b/src/Features/Core/Portable/Completion/Providers/CompletionUtilities.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Recommendations; namespace Microsoft.CodeAnalysis.Completion.Providers { @@ -20,5 +22,15 @@ public static bool IsTypeImplicitlyConvertible(Compilation compilation, ITypeSym return false; } + + public static OptionSet GetUpdatedRecommendationOptions(OptionSet options, string language) + { + var filterOutOfScopeLocals = options.GetOption(CompletionControllerOptions.FilterOutOfScopeLocals); + var hideAdvancedMembers = options.GetOption(CompletionOptions.HideAdvancedMembers, language); + + return options + .WithChangedOption(RecommendationOptions.FilterOutOfScopeLocals, language, filterOutOfScopeLocals) + .WithChangedOption(RecommendationOptions.HideAdvancedMembers, language, hideAdvancedMembers); + } } } diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index 32241da86d8e9..efc1b5bdec458 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -34,6 +34,9 @@ private static CompletionItem CreateWorker( SupportedPlatformData supportedPlatforms = null, ImmutableDictionary properties = null, ImmutableArray tags = default, + string displayTextPrefix = null, + string inlineDescription = null, + Glyph? glyph = null, bool isComplexTextEdit = false) { var props = properties ?? ImmutableDictionary.Empty; @@ -49,10 +52,12 @@ private static CompletionItem CreateWorker( var item = CommonCompletionItem.Create( displayText: displayText, displayTextSuffix: displayTextSuffix, + displayTextPrefix: displayTextPrefix, + inlineDescription: inlineDescription, rules: rules, filterText: filterText ?? (displayText.Length > 0 && displayText[0] == '@' ? displayText : firstSymbol.Name), sortText: sortText ?? firstSymbol.Name, - glyph: firstSymbol.GetGlyph(), + glyph: glyph ?? firstSymbol.GetGlyph(), showsWarningIcon: supportedPlatforms != null, properties: props, tags: tags, @@ -168,28 +173,27 @@ private static ISymbol DecodeSymbol(string id, Compilation compilation) public static async Task GetDescriptionAsync( CompletionItem item, Document document, CancellationToken cancellationToken) { - var workspace = document.Project.Solution.Workspace; + var symbols = await GetSymbolsAsync(item, document, cancellationToken).ConfigureAwait(false); + return await GetDescriptionForSymbolsAsync(item, document, symbols, cancellationToken).ConfigureAwait(false); + } + + public static async Task GetDescriptionForSymbolsAsync( + CompletionItem item, Document document, ImmutableArray symbols, CancellationToken cancellationToken) + { + if (symbols.Length == 0) + return CompletionDescription.Empty; var position = GetDescriptionPosition(item); if (position == -1) - { position = item.Span.Start; - } - var supportedPlatforms = GetSupportedPlatforms(item, workspace); + var workspace = document.Project.Solution.Workspace; + var supportedPlatforms = GetSupportedPlatforms(item, workspace); var contextDocument = FindAppropriateDocumentForDescriptionContext(document, supportedPlatforms); - var semanticModel = await contextDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var symbols = await GetSymbolsAsync(item, document, cancellationToken).ConfigureAwait(false); - if (symbols.Length > 0) - { - return await CommonCompletionUtilities.CreateDescriptionAsync(workspace, semanticModel, position, symbols, supportedPlatforms, cancellationToken).ConfigureAwait(false); - } - else - { - return CompletionDescription.Empty; - } + + return await CommonCompletionUtilities.CreateDescriptionAsync(workspace, semanticModel, position, symbols, supportedPlatforms, cancellationToken).ConfigureAwait(false); } private static Document FindAppropriateDocumentForDescriptionContext(Document document, SupportedPlatformData supportedPlatforms) @@ -281,6 +285,9 @@ public static CompletionItem CreateWithSymbolId( sortText, insertionText, filterText, + displayTextPrefix: null, + inlineDescription: null, + glyph: null, supportedPlatforms, properties, tags, @@ -296,6 +303,9 @@ public static CompletionItem CreateWithSymbolId( string sortText = null, string insertionText = null, string filterText = null, + string displayTextPrefix = null, + string inlineDescription = null, + Glyph? glyph = null, SupportedPlatformData supportedPlatforms = null, ImmutableDictionary properties = null, ImmutableArray tags = default, @@ -304,7 +314,8 @@ public static CompletionItem CreateWithSymbolId( return CreateWorker( displayText, displayTextSuffix, symbols, rules, contextPosition, s_addSymbolEncoding, sortText, insertionText, - filterText, supportedPlatforms, properties, tags, isComplexTextEdit); + filterText, supportedPlatforms, properties, tags, displayTextPrefix, + inlineDescription, glyph, isComplexTextEdit); } public static CompletionItem CreateWithNameAndKind( @@ -316,6 +327,9 @@ public static CompletionItem CreateWithNameAndKind( string sortText = null, string insertionText = null, string filterText = null, + string displayTextPrefix = null, + string inlineDescription = null, + Glyph? glyph = null, SupportedPlatformData supportedPlatforms = null, ImmutableDictionary properties = null, ImmutableArray tags = default, @@ -324,7 +338,8 @@ public static CompletionItem CreateWithNameAndKind( return CreateWorker( displayText, displayTextSuffix, symbols, rules, contextPosition, s_addSymbolInfo, sortText, insertionText, - filterText, supportedPlatforms, properties, tags, isComplexTextEdit); + filterText, supportedPlatforms, properties, tags, + displayTextPrefix, inlineDescription, glyph, isComplexTextEdit); } internal static string GetSymbolName(CompletionItem item) diff --git a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs index e6199e5adc1d2..30efb157b15cd 100644 --- a/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs @@ -825,21 +825,15 @@ private static void AddConversions( generator.ObjectCreationExpression(structType, arguments)); members.Add(CodeGenerationSymbolFactory.CreateConversionSymbol( - attributes: default, - Accessibility.Public, - DeclarationModifiers.Static, tupleType, CodeGenerationSymbolFactory.CreateParameterSymbol(structType, ValueName), isImplicit: true, - ImmutableArray.Create(convertToTupleStatement))); + statements: ImmutableArray.Create(convertToTupleStatement))); members.Add(CodeGenerationSymbolFactory.CreateConversionSymbol( - attributes: default, - Accessibility.Public, - DeclarationModifiers.Static, structType, CodeGenerationSymbolFactory.CreateParameterSymbol(tupleType, ValueName), isImplicit: true, - ImmutableArray.Create(convertToStructStatement))); + statements: ImmutableArray.Create(convertToStructStatement))); } private static INamedTypeSymbol CreateNamedType( diff --git a/src/Features/Core/Portable/ExternalAccess/Pythia/Api/PythiaCompletionProviderBase.cs b/src/Features/Core/Portable/ExternalAccess/Pythia/Api/PythiaCompletionProviderBase.cs index 47e9aebcec5bd..4868b5ccffa68 100644 --- a/src/Features/Core/Portable/ExternalAccess/Pythia/Api/PythiaCompletionProviderBase.cs +++ b/src/Features/Core/Portable/ExternalAccess/Pythia/Api/PythiaCompletionProviderBase.cs @@ -42,7 +42,8 @@ public static CompletionItem CreateSymbolCompletionItem( SupportedPlatformData? supportedPlatforms = null, ImmutableDictionary? properties = null, ImmutableArray tags = default) - => SymbolCompletionItem.CreateWithSymbolId(displayText, displayTextSuffix: null, symbols, rules, contextPosition, sortText, insertionText, filterText, supportedPlatforms, properties, tags); + => SymbolCompletionItem.CreateWithSymbolId(displayText, displayTextSuffix: null, symbols, rules, contextPosition, sortText, insertionText, + filterText, displayTextPrefix: null, inlineDescription: null, glyph: null, supportedPlatforms, properties, tags); public static ImmutableArray CreateRecommendedKeywordDisplayParts(string keyword, string toolTip) => RecommendedKeyword.CreateDisplayParts(keyword, toolTip); diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 755299062dae6..f7fed82a68109 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2790,6 +2790,9 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Pull member(s) up to new base class... + + Operators + The assembly '{0}' containing type '{1}' references .NET Framework, which is not supported. diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index 62784bb866f8b..e0458264c7c25 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -82,8 +82,7 @@ protected abstract partial class AbstractSymbolDescriptionBuilder private readonly SemanticModel _semanticModel; private readonly int _position; private readonly IAnonymousTypeDisplayService _anonymousTypeDisplayService; - private readonly Dictionary> _groupMap = - new(); + private readonly Dictionary> _groupMap = new(); protected readonly Workspace Workspace; protected readonly CancellationToken CancellationToken; @@ -106,6 +105,8 @@ protected AbstractSymbolDescriptionBuilder( protected abstract void AddAwaitableExtensionPrefix(); protected abstract void AddDeprecatedPrefix(); protected abstract Task> GetInitializerSourcePartsAsync(ISymbol symbol); + protected abstract ImmutableArray ToMinimalDisplayParts(ISymbol symbol, SemanticModel semanticModel, int position, SymbolDisplayFormat format); + protected abstract string GetNavigationHint(ISymbol symbol); protected abstract SymbolDisplayFormat MinimallyQualifiedFormat { get; } protected abstract SymbolDisplayFormat MinimallyQualifiedFormatWithConstants { get; } @@ -372,7 +373,7 @@ private static int GetPrecedingNewLineCount(SymbolDescriptionGroups group) } private IDictionary> BuildDescriptionSections() - => _groupMap.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToTaggedText()); + => _groupMap.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToTaggedText(GetNavigationHint)); private void AddDescriptionForDynamicType() { @@ -716,7 +717,7 @@ protected static IEnumerable Space(int count = 1) protected ImmutableArray ToMinimalDisplayParts(ISymbol symbol, SymbolDisplayFormat format = null) { format ??= MinimallyQualifiedFormat; - return symbol.ToMinimalDisplayParts(_semanticModel, _position, format); + return ToMinimalDisplayParts(symbol, _semanticModel, _position, format); } private static IEnumerable Part(SymbolDisplayPartKind kind, ISymbol symbol, string text) diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index e86632f78b245..69608076d811e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -665,6 +665,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Nedostatek znaků ) This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated Odkaz na vlastnost se nedá aktualizovat. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index cd5fedcbbc36b..33ea204d3e6fa 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -665,6 +665,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Zu wenige )-Zeichen This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated Der Eigenschaftenverweis kann nicht aktualisiert werden. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index d55ef5c4d3937..6ff91d3d872a1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -665,6 +665,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa No hay suficientes ) This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated No se puede actualizar la referencia de propiedad diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 4b1f6d89ae7c1..2eb361c400676 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -665,6 +665,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Pas assez de )'s This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated La référence de propriété ne peut pas être mise à jour diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index bf1d62bf9f144..5b88153f51a69 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -665,6 +665,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Parentesi chiuse insufficienti This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated Non è possibile aggiornare il riferimento alla proprietà diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 3448bb0bef64e..8cc987913e156 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -665,6 +665,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma ) が足りません This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated プロパティ参照を更新できません diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 4d278c044dfb4..52ec983bcf4c2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -665,6 +665,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 부족 )'s This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated 속성 참조를 업데이트할 수 없습니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 518750d3cbb53..01715afa49315 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -665,6 +665,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zbyt mało znaków ) This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated Nie można zaktualizować referencji właściwości diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 9c1a113fdf848..9f47d72112c43 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -665,6 +665,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Não há )'s suficientes This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated A referência de propriedade não pode ser atualizada diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 6bd6bb7a0cef6..ed2017e8c9a19 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -665,6 +665,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Отсутствуют закрывающие круглые скобки This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated Не удается обновить ссылку на свойство diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 8ec967f1d7f53..577c3356c3d06 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -665,6 +665,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Değil yeterli)'ın This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated Özellik başvurusu güncelleştirilemiyor diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 503b86d5b6963..b5864eec4d854 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -665,6 +665,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma ")" 不足 This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated 无法更新属性引用 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 145d676889050..6be794840f370 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -665,6 +665,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma ) 不夠 This is an error message shown to the user when they write an invalid Regular Expression. Example: (a + + Operators + Operators + + Property reference cannot be updated 無法更新屬性參考 diff --git a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb index 0773783a2fa3f..7f6753a278bf7 100644 --- a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb +++ b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb @@ -78,6 +78,14 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices Return SpecializedTasks.EmptyImmutableArray(Of SymbolDisplayPart) End Function + Protected Overrides Function ToMinimalDisplayParts(symbol As ISymbol, semanticModel As SemanticModel, position As Integer, format As SymbolDisplayFormat) As ImmutableArray(Of SymbolDisplayPart) + Return CodeAnalysis.VisualBasic.ToMinimalDisplayParts(symbol, semanticModel, position, format) + End Function + + Protected Overrides Function GetNavigationHint(symbol As ISymbol) As String + Return If(symbol Is Nothing, Nothing, CodeAnalysis.VisualBasic.SymbolDisplay.ToDisplayString(symbol, SymbolDisplayFormat.MinimallyQualifiedFormat)) + End Function + Private Async Function GetFirstDeclarationAsync(Of T As SyntaxNode)(symbol As ISymbol) As Task(Of T) For Each syntaxRef In symbol.DeclaringSyntaxReferences Dim syntax = Await syntaxRef.GetSyntaxAsync(Me.CancellationToken).ConfigureAwait(False) diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs index b6cce44511d30..12d497e6061eb 100644 --- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs @@ -12,13 +12,14 @@ using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Recommendations; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Recommendations { - internal class CSharpRecommendationServiceRunner : AbstractRecommendationServiceRunner + internal partial class CSharpRecommendationServiceRunner : AbstractRecommendationServiceRunner { public CSharpRecommendationServiceRunner( CSharpSyntaxContext context, bool filterOutOfScopeLocals, CancellationToken cancellationToken) @@ -26,17 +27,18 @@ public CSharpRecommendationServiceRunner( { } - public override ImmutableArray GetSymbols() + public override RecommendedSymbols GetRecommendedSymbols() { if (_context.IsInNonUserCode || _context.IsPreProcessorDirectiveContext) { - return ImmutableArray.Empty; + return default; } - return _context.IsRightOfNameSeparator - ? GetSymbolsOffOfContainer() - : GetSymbolsForCurrentContext(); + if (!_context.IsRightOfNameSeparator) + return new RecommendedSymbols(GetSymbolsForCurrentContext()); + + return GetSymbolsOffOfContainer(); } public override bool TryGetExplicitTypeOfLambdaParameter(SyntaxNode lambdaSyntax, int ordinalInLambda, [NotNullWhen(true)] out ITypeSymbol? explicitLambdaParameterType) @@ -101,7 +103,7 @@ private ImmutableArray GetSymbolsForCurrentContext() return ImmutableArray.Empty; } - private ImmutableArray GetSymbolsOffOfContainer() + private RecommendedSymbols GetSymbolsOffOfContainer() { // Ensure that we have the correct token in A.B| case var node = _context.TargetToken.GetRequiredParent(); @@ -117,7 +119,7 @@ private ImmutableArray GetSymbolsOffOfContainer() QualifiedNameSyntax qualifiedName => GetSymbolsOffOfName(qualifiedName.Left), AliasQualifiedNameSyntax aliasName => GetSymbolsOffOffAlias(aliasName.Alias), MemberBindingExpressionSyntax _ => GetSymbolsOffOfConditionalReceiver(node.GetParentConditionalAccessExpression()!.Expression), - _ => ImmutableArray.Empty, + _ => default, }; } @@ -167,17 +169,15 @@ private ImmutableArray GetSymbolsForTypeArgumentOfConstraintClause() return ImmutableArray.CastUp(symbols); } - private ImmutableArray GetSymbolsOffOffAlias(IdentifierNameSyntax alias) + private RecommendedSymbols GetSymbolsOffOffAlias(IdentifierNameSyntax alias) { var aliasSymbol = _context.SemanticModel.GetAliasInfo(alias, _cancellationToken); if (aliasSymbol == null) - { - return ImmutableArray.Empty; - } + return default; - return _context.SemanticModel.LookupNamespacesAndTypes( + return new RecommendedSymbols(_context.SemanticModel.LookupNamespacesAndTypes( alias.SpanStart, - aliasSymbol.Target); + aliasSymbol.Target)); } private ImmutableArray GetSymbolsForLabelContext() @@ -247,7 +247,7 @@ private ImmutableArray GetSymbolsForExpressionOrStatementContext() return symbols; } - private ImmutableArray GetSymbolsOffOfName(NameSyntax name) + private RecommendedSymbols GetSymbolsOffOfName(NameSyntax name) { // Using an is pattern on an enum is a qualified name, but normal symbol processing works fine if (_context.IsEnumTypeMemberAccessContext) @@ -296,7 +296,7 @@ private ImmutableArray GetSymbolsOffOfName(NameSyntax name) { if (_context.IsNameOfContext) { - return _context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); + return new RecommendedSymbols(_context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol)); } var symbols = _context.SemanticModel.LookupNamespacesAndTypes( @@ -306,7 +306,7 @@ private ImmutableArray GetSymbolsOffOfName(NameSyntax name) if (_context.IsNamespaceDeclarationNameContext) { var declarationSyntax = name.GetAncestorOrThis(); - return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); + return new RecommendedSymbols(symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax))); } // Filter the types when in a using directive, but not an alias. @@ -319,21 +319,21 @@ private ImmutableArray GetSymbolsOffOfName(NameSyntax name) var usingDirective = name.GetAncestorOrThis(); if (usingDirective != null && usingDirective.Alias == null) { - return usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword) + return new RecommendedSymbols(usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword) ? symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()) - : symbols.WhereAsArray(s => s.IsNamespace()); + : symbols.WhereAsArray(s => s.IsNamespace())); } - return symbols; + return new RecommendedSymbols(symbols); } - return ImmutableArray.Empty; + return default; } - private ImmutableArray GetSymbolsOffOfExpression(ExpressionSyntax? originalExpression) + private RecommendedSymbols GetSymbolsOffOfExpression(ExpressionSyntax? originalExpression) { if (originalExpression == null) - return ImmutableArray.Empty; + return default; // In case of 'await x$$', we want to move to 'x' to get it's members. // To run GetSymbolInfo, we also need to get rid of parenthesis. @@ -344,7 +344,7 @@ private ImmutableArray GetSymbolsOffOfExpression(ExpressionSyntax? orig var leftHandBinding = _context.SemanticModel.GetSymbolInfo(expression, _cancellationToken); var container = _context.SemanticModel.GetTypeInfo(expression, _cancellationToken).Type; - var normalSymbols = GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); + var result = GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); // Check for the Color Color case. if (originalExpression.CanAccessInstanceAndStaticMembersOffOf(_context.SemanticModel, _cancellationToken)) @@ -353,13 +353,15 @@ private ImmutableArray GetSymbolsOffOfExpression(ExpressionSyntax? orig var typeMembers = GetSymbolsOffOfBoundExpression(originalExpression, expression, speculativeSymbolInfo, container); - normalSymbols = normalSymbols.Concat(typeMembers); + result = new RecommendedSymbols( + result.NamedSymbols.Concat(typeMembers.NamedSymbols), + result.UnnamedSymbols); } - return normalSymbols; + return result; } - private ImmutableArray GetSymbolsOffOfDereferencedExpression(ExpressionSyntax originalExpression) + private RecommendedSymbols GetSymbolsOffOfDereferencedExpression(ExpressionSyntax originalExpression) { var expression = originalExpression.WalkDownParentheses(); var leftHandBinding = _context.SemanticModel.GetSymbolInfo(expression, _cancellationToken); @@ -373,7 +375,7 @@ private ImmutableArray GetSymbolsOffOfDereferencedExpression(Expression return GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); } - private ImmutableArray GetSymbolsOffOfConditionalReceiver(ExpressionSyntax originalExpression) + private RecommendedSymbols GetSymbolsOffOfConditionalReceiver(ExpressionSyntax originalExpression) { // Given ((T?)t)?.|, the '.' will behave as if the expression was actually ((T)t).|. More plainly, // a member access off of a conditional receiver of nullable type binds to the unwrapped nullable @@ -386,14 +388,12 @@ private ImmutableArray GetSymbolsOffOfConditionalReceiver(ExpressionSyn // If the thing on the left is a type, namespace, or alias, we shouldn't show anything in // IntelliSense. if (leftHandBinding.GetBestOrAllSymbols().FirstOrDefault().MatchesKind(SymbolKind.NamedType, SymbolKind.Namespace, SymbolKind.Alias)) - { - return ImmutableArray.Empty; - } + return default; return GetSymbolsOffOfBoundExpression(originalExpression, expression, leftHandBinding, container); } - private ImmutableArray GetSymbolsOffOfBoundExpression( + private RecommendedSymbols GetSymbolsOffOfBoundExpression( ExpressionSyntax originalExpression, ExpressionSyntax expression, SymbolInfo leftHandBinding, @@ -409,7 +409,7 @@ private ImmutableArray GetSymbolsOffOfBoundExpression( { // If the thing on the left is a lambda expression, we shouldn't show anything. if (symbol is IMethodSymbol { MethodKind: MethodKind.AnonymousFunction }) - return ImmutableArray.Empty; + return default; var originalExpressionKind = originalExpression.Kind(); @@ -418,21 +418,21 @@ private ImmutableArray GetSymbolsOffOfBoundExpression( if (originalExpressionKind is SyntaxKind.ParenthesizedExpression && symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace or SymbolKind.Alias) { - return ImmutableArray.Empty; + return default; } // If the thing on the left is a method name identifier, we shouldn't show anything. if (symbol.Kind is SymbolKind.Method && originalExpressionKind is SyntaxKind.IdentifierName or SyntaxKind.GenericName) { - return ImmutableArray.Empty; + return default; } // If the thing on the left is an event that can't be used as a field, we shouldn't show anything if (symbol is IEventSymbol ev && !_context.SemanticModel.IsEventUsableAsField(originalExpression.SpanStart, ev)) { - return ImmutableArray.Empty; + return default; } if (symbol is IAliasSymbol alias) @@ -454,7 +454,7 @@ private ImmutableArray GetSymbolsOffOfBoundExpression( if (symbol is IParameterSymbol parameter) { if (parameter.IsThis && expression.IsInStaticContext()) - return ImmutableArray.Empty; + return default; containerSymbol = symbol; } @@ -467,7 +467,7 @@ private ImmutableArray GetSymbolsOffOfBoundExpression( } if (containerSymbol == null) - return ImmutableArray.Empty; + return default; Debug.Assert(!excludeInstance || !excludeStatic); @@ -483,9 +483,51 @@ private ImmutableArray GetSymbolsOffOfBoundExpression( var symbols = GetMemberSymbols(containerSymbol, position: originalExpression.SpanStart, excludeInstance, useBaseReferenceAccessibility); // If we're showing instance members, don't include nested types - return excludeStatic + var namedSymbols = excludeStatic ? symbols.WhereAsArray(s => !(s.IsStatic || s is ITypeSymbol)) : symbols; + + // if we're dotting off an instance, then add potential operators/indexers/conversions that may be + // applicable to it as well. + var unnamedSymbols = _context.IsNameOfContext || excludeInstance + ? default + : GetUnnamedSymbols(originalExpression); + return new RecommendedSymbols(namedSymbols, unnamedSymbols); + } + + private ImmutableArray GetUnnamedSymbols(ExpressionSyntax originalExpression) + { + using var _ = ArrayBuilder.GetInstance(out var symbols); + + var semanticModel = _context.SemanticModel; + var container = semanticModel.GetTypeInfo(originalExpression, _cancellationToken).Type; + if (container == null) + return ImmutableArray.Empty; + + // In a case like `x?.Y` if we bind the type of `.Y` we will get a value type back (like `int`), and not + // `int?`. However, we want to think of the constructed type as that's the type of the overall expression + // that will be casted. + if (originalExpression.GetRootConditionalAccessExpression() != null) + container = TryMakeNullable(semanticModel.Compilation, container); + + AddIndexers(container, symbols); + AddOperators(container, symbols); + AddConversions(container, symbols); + + return symbols.ToImmutable(); + } + + private void AddIndexers(ITypeSymbol container, ArrayBuilder symbols) + { + var containingType = _context.SemanticModel.GetEnclosingNamedType(_context.Position, _cancellationToken); + if (containingType == null) + return; + + foreach (var member in container.RemoveNullableIfPresent().GetAccessibleMembersInThisAndBaseTypes(containingType)) + { + if (member.IsIndexer) + symbols.Add(member); + } } } } diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner_Conversions.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner_Conversions.cs new file mode 100644 index 0000000000000..e1b741e7936b1 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner_Conversions.cs @@ -0,0 +1,269 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.Recommendations +{ + /// + /// Adds user defined and predefined conversions to the unnamed recommendation set. + /// + internal partial class CSharpRecommendationServiceRunner + { + private static readonly ImmutableArray s_predefinedEnumConversionTargets = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_Decimal, + SpecialType.System_Double, + SpecialType.System_Single, + SpecialType.System_Int32, + SpecialType.System_Int64, + SpecialType.System_SByte, + SpecialType.System_Int16, + SpecialType.System_UInt32, + SpecialType.System_UInt64, + SpecialType.System_UInt16); + + private static readonly ImmutableArray s_sbyteConversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_UInt32, + SpecialType.System_UInt64, + SpecialType.System_UInt16); + + private static readonly ImmutableArray s_byteConversions = ImmutableArray.Create( + SpecialType.System_Char, + SpecialType.System_SByte); + + private static readonly ImmutableArray s_int16Conversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_UInt32, + SpecialType.System_UInt64, + SpecialType.System_UInt16, + SpecialType.System_SByte); + + private static readonly ImmutableArray s_uint16Conversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_SByte, + SpecialType.System_Int16); + + private static readonly ImmutableArray s_int32Conversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_SByte, + SpecialType.System_Int16, + SpecialType.System_UInt32, + SpecialType.System_UInt16, + SpecialType.System_UInt64); + + private static readonly ImmutableArray s_uint32Conversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_Int32, + SpecialType.System_SByte, + SpecialType.System_Int16, + SpecialType.System_UInt16); + + private static readonly ImmutableArray s_int64Conversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_Int32, + SpecialType.System_UInt32, + SpecialType.System_UInt64, + SpecialType.System_UInt16, + SpecialType.System_SByte, + SpecialType.System_Int16); + + private static readonly ImmutableArray s_uint64Conversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_Int32, + SpecialType.System_Int64, + SpecialType.System_UInt32, + SpecialType.System_UInt16, + SpecialType.System_SByte, + SpecialType.System_Int16); + + private static readonly ImmutableArray s_charConversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_SByte, + SpecialType.System_Int16); + + private static readonly ImmutableArray s_singleConversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_Decimal, + SpecialType.System_Int32, + SpecialType.System_Int64, + SpecialType.System_UInt32, + SpecialType.System_UInt64, + SpecialType.System_UInt16, + SpecialType.System_SByte, + SpecialType.System_Int16); + + private static readonly ImmutableArray s_doubleConversions = ImmutableArray.Create( + SpecialType.System_Byte, + SpecialType.System_Char, + SpecialType.System_Decimal, + SpecialType.System_Single, + SpecialType.System_Int32, + SpecialType.System_Int64, + SpecialType.System_UInt32, + SpecialType.System_UInt64, + SpecialType.System_UInt16, + SpecialType.System_SByte, + SpecialType.System_Int16); + + private void AddConversions(ITypeSymbol container, ArrayBuilder symbols) + { + if (container.RemoveNullableIfPresent() is INamedTypeSymbol namedType) + { + AddUserDefinedConversionsOfType(container, namedType, symbols); + AddBuiltInNumericConversions(container, namedType, symbols); + AddBuiltInEnumConversions(container, namedType, symbols); + } + } + + private static ITypeSymbol TryMakeNullable(Compilation compilation, ITypeSymbol container) + { + return container.IsNonNullableValueType() + ? compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(container) + : container; + } + + private void AddUserDefinedConversionsOfType( + ITypeSymbol container, INamedTypeSymbol containerWithoutNullable, ArrayBuilder symbols) + { + var compilation = _context.SemanticModel.Compilation; + var containerIsNullable = container.IsNullable(); + + foreach (var type in containerWithoutNullable.GetBaseTypesAndThis()) + { + foreach (var member in type.GetMembers(WellKnownMemberNames.ExplicitConversionName)) + { + if (member is not IMethodSymbol method) + continue; + + if (!method.IsConversion()) + continue; + + if (method.Parameters.Length != 1) + continue; + + // Has to be a conversion that actually converts the type we're operating on. + if (!type.Equals(method.Parameters[0].Type)) + continue; + + // If this is a nullable context, then 'lift' the conversion so we offer the nullable form of it to + // the user instead. + symbols.Add(containerIsNullable && IsLiftableConversion(method) + ? LiftConversion(compilation, method) + : method); + } + } + + return; + + // https://github.com/dotnet/csharplang/blob/main/spec/conversions.md#lifted-conversion-operators + // + // Given a user-defined conversion operator that converts from a non-nullable value type S to a non-nullable + // value type T, a lifted conversion operator exists that converts from S? to T? + static bool IsLiftableConversion(IMethodSymbol method) + => method.ReturnType.IsNonNullableValueType() && method.Parameters.Single().Type.IsNonNullableValueType(); + } + + private IMethodSymbol LiftConversion(Compilation compilation, IMethodSymbol method) + => CreateConversion( + method.ContainingType, + TryMakeNullable(compilation, method.Parameters.Single().Type), + TryMakeNullable(compilation, method.ReturnType), + method.GetDocumentationCommentXml(cancellationToken: _cancellationToken)); + + private void AddBuiltInNumericConversions( + ITypeSymbol container, INamedTypeSymbol containerWithoutNullable, ArrayBuilder symbols) + { + var conversions = GetPredefinedNumericConversions(containerWithoutNullable); + if (!conversions.HasValue) + return; + + AddCompletionItemsForSpecialTypes(container, containerWithoutNullable, symbols, conversions.Value); + } + + public static ImmutableArray? GetPredefinedNumericConversions(ITypeSymbol container) + => container.SpecialType switch + { + SpecialType.System_SByte => s_sbyteConversions, + SpecialType.System_Byte => s_byteConversions, + SpecialType.System_Int16 => s_int16Conversions, + SpecialType.System_UInt16 => s_uint16Conversions, + SpecialType.System_Int32 => s_int32Conversions, + SpecialType.System_UInt32 => s_uint32Conversions, + SpecialType.System_Int64 => s_int64Conversions, + SpecialType.System_UInt64 => s_uint64Conversions, + SpecialType.System_Char => s_charConversions, + SpecialType.System_Single => s_singleConversions, + SpecialType.System_Double => s_doubleConversions, + // Decimal intentionally not here as it exposes its conversions as normal methods in the symbol model. + _ => null, + }; + + private void AddCompletionItemsForSpecialTypes( + ITypeSymbol container, INamedTypeSymbol containerWithoutNullable, ArrayBuilder symbols, ImmutableArray specialTypes) + { + var compilation = _context.SemanticModel.Compilation; + + foreach (var specialType in specialTypes) + { + var targetTypeSymbol = _context.SemanticModel.Compilation.GetSpecialType(specialType); + var conversion = CreateConversion( + containerWithoutNullable, fromType: containerWithoutNullable, toType: targetTypeSymbol, + CreateConversionDocumentationCommentXml(containerWithoutNullable, targetTypeSymbol)); + + symbols.Add(container.IsNullable() ? LiftConversion(compilation, conversion) : conversion); + } + + return; + + static string CreateConversionDocumentationCommentXml(ITypeSymbol fromType, ITypeSymbol toType) + { + var summary = string.Format(WorkspacesResources.Predefined_conversion_from_0_to_1, + SeeTag(fromType.GetDocumentationCommentId()), + SeeTag(toType.GetDocumentationCommentId())); + + return $"{summary}"; + + static string SeeTag(string? id) + => $@""; + } + } + + private static IMethodSymbol CreateConversion(INamedTypeSymbol containingType, ITypeSymbol fromType, ITypeSymbol toType, string? documentationCommentXml) + => CodeGenerationSymbolFactory.CreateConversionSymbol( + toType: toType, + fromType: CodeGenerationSymbolFactory.CreateParameterSymbol(fromType, "value"), + containingType: containingType, + documentationCommentXml: documentationCommentXml); + + private void AddBuiltInEnumConversions( + ITypeSymbol container, INamedTypeSymbol containerWithoutNullable, ArrayBuilder symbols) + { + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#explicit-enumeration-conversions + // Three kinds of conversions are defined in the spec. + // Suggestion are made for one kind: + // * From any enum_type to sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, or decimal. + // No suggestion for the other two kinds of conversions: + // * From sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, or decimal to any enum_type. + // * From any enum_type to any other enum_type. + + if (containerWithoutNullable.IsEnumType()) + AddCompletionItemsForSpecialTypes(container, containerWithoutNullable, symbols, s_predefinedEnumConversionTargets); + } + } +} diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner_Operators.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner_Operators.cs new file mode 100644 index 0000000000000..125d13e82fd86 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner_Operators.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Recommendations +{ + /// + /// Adds user defined operators to the unnamed recommendation set. + /// + internal partial class CSharpRecommendationServiceRunner + { + private static void AddOperators(ITypeSymbol container, ArrayBuilder symbols) + { + var containerWithoutNullable = container.RemoveNullableIfPresent(); + + // Don't bother showing operators for basic built-in types. They're well known already and will only + // clutter the display. + if (ExcludeOperatorType(containerWithoutNullable)) + return; + + var containerIsNullable = container.IsNullable(); + foreach (var type in containerWithoutNullable.GetBaseTypesAndThis()) + { + foreach (var member in type.GetMembers()) + { + if (member is not IMethodSymbol { MethodKind: MethodKind.UserDefinedOperator } method) + continue; + + // Don't add operator true/false. They only are used for conversions in special boolean contexts + // (like `if` statement conditions), and are not invoked explicitly by the user. + if (method.Name is WellKnownMemberNames.TrueOperatorName or WellKnownMemberNames.FalseOperatorName) + continue; + + // If we're on a nullable version of the type, but this operator wouldn't naturally 'lift' to be + // available for it, then don't include it. + if (containerIsNullable && !IsLiftableOperator(method)) + continue; + + // We don't need to bother lifting operators. We'll just show the basic operator in the list as the + // information for it is sufficient for completion (i.e. we only insert the operator itself, not any + // of the parameter or return types). + symbols.Add(method); + } + } + } + + private static bool ExcludeOperatorType(ITypeSymbol container) + => container.IsSpecialType() || container.SpecialType is SpecialType.System_IntPtr or SpecialType.System_UIntPtr; + + private static bool IsLiftableOperator(IMethodSymbol symbol) + { + // https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#lifted-operators + + // Common for all: + if (symbol.IsUserDefinedOperator() && symbol.Parameters.All(p => p.Type.IsValueType)) + { + switch (symbol.Name) + { + // Unary + case WellKnownMemberNames.UnaryPlusOperatorName: + case WellKnownMemberNames.IncrementOperatorName: + case WellKnownMemberNames.UnaryNegationOperatorName: + case WellKnownMemberNames.DecrementOperatorName: + case WellKnownMemberNames.LogicalNotOperatorName: + case WellKnownMemberNames.OnesComplementOperatorName: + return symbol.Parameters.Length == 1 && symbol.ReturnType.IsValueType; + + // Binary + case WellKnownMemberNames.AdditionOperatorName: + case WellKnownMemberNames.SubtractionOperatorName: + case WellKnownMemberNames.MultiplyOperatorName: + case WellKnownMemberNames.DivisionOperatorName: + case WellKnownMemberNames.ModulusOperatorName: + case WellKnownMemberNames.BitwiseAndOperatorName: + case WellKnownMemberNames.BitwiseOrOperatorName: + case WellKnownMemberNames.ExclusiveOrOperatorName: + case WellKnownMemberNames.LeftShiftOperatorName: + case WellKnownMemberNames.RightShiftOperatorName: + return symbol.Parameters.Length == 2 && symbol.ReturnType.IsValueType; + + // Equality + Relational + case WellKnownMemberNames.EqualityOperatorName: + case WellKnownMemberNames.InequalityOperatorName: + case WellKnownMemberNames.LessThanOperatorName: + case WellKnownMemberNames.GreaterThanOperatorName: + case WellKnownMemberNames.LessThanOrEqualOperatorName: + case WellKnownMemberNames.GreaterThanOrEqualOperatorName: + return symbol.Parameters.Length == 2 && symbol.ReturnType.SpecialType == SpecialType.System_Boolean; + } + } + + return false; + } + } +} diff --git a/src/Workspaces/Core/Portable/CodeGeneration/CodeGenerationSymbolFactory.cs b/src/Workspaces/Core/Portable/CodeGeneration/CodeGenerationSymbolFactory.cs index f00a9bf269401..c5e4b38594aa6 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/CodeGenerationSymbolFactory.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/CodeGenerationSymbolFactory.cs @@ -160,7 +160,7 @@ internal static IMethodSymbol CreateMethodSymbol( MethodKind methodKind = MethodKind.Ordinary, bool isInitOnly = false) { - var result = new CodeGenerationMethodSymbol(containingType, attributes, accessibility, modifiers, returnType, refKind, explicitInterfaceImplementations, name, typeParameters, parameters, returnTypeAttributes, methodKind, isInitOnly); + var result = new CodeGenerationMethodSymbol(containingType, attributes, accessibility, modifiers, returnType, refKind, explicitInterfaceImplementations, name, typeParameters, parameters, returnTypeAttributes, documentationCommentXml: null, methodKind, isInitOnly); CodeGenerationMethodInfo.Attach(result, modifiers.IsNew, modifiers.IsUnsafe, modifiers.IsPartial, modifiers.IsAsync, statements, handlesExpressions); return result; } @@ -195,7 +195,8 @@ public static IMethodSymbol CreateOperatorSymbol( CodeGenerationOperatorKind operatorKind, ImmutableArray parameters, ImmutableArray statements = default, - ImmutableArray returnTypeAttributes = default) + ImmutableArray returnTypeAttributes = default, + string? documentationCommentXml = null) { var expectedParameterCount = CodeGenerationOperatorSymbol.GetParameterCount(operatorKind); if (parameters.Length != expectedParameterCount) @@ -206,11 +207,36 @@ public static IMethodSymbol CreateOperatorSymbol( throw new ArgumentException(message, nameof(parameters)); } - var result = new CodeGenerationOperatorSymbol(null, attributes, accessibility, modifiers, returnType, operatorKind, parameters, returnTypeAttributes); + var result = new CodeGenerationOperatorSymbol(null, attributes, accessibility, modifiers, returnType, operatorKind, parameters, returnTypeAttributes, documentationCommentXml); CodeGenerationMethodInfo.Attach(result, modifiers.IsNew, modifiers.IsUnsafe, modifiers.IsPartial, modifiers.IsAsync, statements, handlesExpressions: default); return result; } + /// + /// Creates a method symbol that can be used to describe a conversion declaration. + /// + public static IMethodSymbol CreateConversionSymbol( + ITypeSymbol toType, + IParameterSymbol fromType, + INamedTypeSymbol? containingType = null, + bool isImplicit = false, + ImmutableArray statements = default, + ImmutableArray toTypeAttributes = default, + string? documentationCommentXml = null) + { + return CreateConversionSymbol( + attributes: default, + accessibility: Accessibility.Public, + DeclarationModifiers.Static, + toType, + fromType, + containingType, + isImplicit, + statements, + toTypeAttributes, + documentationCommentXml); + } + /// /// Creates a method symbol that can be used to describe a conversion declaration. /// @@ -220,11 +246,13 @@ public static IMethodSymbol CreateConversionSymbol( DeclarationModifiers modifiers, ITypeSymbol toType, IParameterSymbol fromType, + INamedTypeSymbol? containingType = null, bool isImplicit = false, ImmutableArray statements = default, - ImmutableArray toTypeAttributes = default) + ImmutableArray toTypeAttributes = default, + string? documentationCommentXml = null) { - var result = new CodeGenerationConversionSymbol(null, attributes, accessibility, modifiers, toType, fromType, isImplicit, toTypeAttributes); + var result = new CodeGenerationConversionSymbol(containingType, attributes, accessibility, modifiers, toType, fromType, isImplicit, toTypeAttributes, documentationCommentXml); CodeGenerationMethodInfo.Attach(result, modifiers.IsNew, modifiers.IsUnsafe, modifiers.IsPartial, modifiers.IsAsync, statements, handlesExpressions: default); return result; } diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 413f0bd115e45..c040bccc0cdd0 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -25,8 +25,9 @@ protected CodeGenerationAbstractMethodSymbol( Accessibility declaredAccessibility, DeclarationModifiers modifiers, string name, - ImmutableArray returnTypeAttributes) - : base(containingType?.ContainingAssembly, containingType, attributes, declaredAccessibility, modifiers, name) + ImmutableArray returnTypeAttributes, + string documentationCommentXml = null) + : base(containingType?.ContainingAssembly, containingType, attributes, declaredAccessibility, modifiers, name, documentationCommentXml) { _returnTypeAttributes = returnTypeAttributes.NullToEmpty(); } diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConversionSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConversionSymbol.cs index 35bcb834cdae7..e8d7d0eecc34e 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConversionSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConversionSymbol.cs @@ -19,7 +19,8 @@ public CodeGenerationConversionSymbol( ITypeSymbol toType, IParameterSymbol fromType, bool isImplicit, - ImmutableArray toTypeAttributes) + ImmutableArray toTypeAttributes, + string documentationCommentXml) : base(containingType, attributes, declaredAccessibility, @@ -32,7 +33,8 @@ public CodeGenerationConversionSymbol( WellKnownMemberNames.ExplicitConversionName, typeParameters: ImmutableArray.Empty, parameters: ImmutableArray.Create(fromType), - returnTypeAttributes: toTypeAttributes) + returnTypeAttributes: toTypeAttributes, + documentationCommentXml) { } diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs index 6eb2470920695..f17a9dabd284c 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs @@ -31,9 +31,10 @@ public CodeGenerationMethodSymbol( ImmutableArray typeParameters, ImmutableArray parameters, ImmutableArray returnTypeAttributes, + string documentationCommentXml = null, MethodKind methodKind = MethodKind.Ordinary, bool isInitOnly = false) - : base(containingType, attributes, declaredAccessibility, modifiers, name, returnTypeAttributes) + : base(containingType, attributes, declaredAccessibility, modifiers, name, returnTypeAttributes, documentationCommentXml) { this.ReturnType = returnType; this.RefKind = refKind; @@ -55,7 +56,7 @@ protected override CodeGenerationSymbol Clone() this.GetAttributes(), this.DeclaredAccessibility, this.Modifiers, this.ReturnType, this.RefKind, this.ExplicitInterfaceImplementations, this.Name, this.TypeParameters, this.Parameters, this.GetReturnTypeAttributes(), - this.MethodKind, this.IsInitOnly); + _documentationCommentXml, this.MethodKind, this.IsInitOnly); CodeGenerationMethodInfo.Attach(result, CodeGenerationMethodInfo.GetIsNew(this), diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationOperatorSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationOperatorSymbol.cs index b34eff33ddafa..2e99829026b52 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationOperatorSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationOperatorSymbol.cs @@ -20,18 +20,20 @@ public CodeGenerationOperatorSymbol( ITypeSymbol returnType, CodeGenerationOperatorKind operatorKind, ImmutableArray parameters, - ImmutableArray returnTypeAttributes) + ImmutableArray returnTypeAttributes, + string documentationCommentXml) : base(containingType, attributes, accessibility, modifiers, - returnType: returnType, + returnType, refKind: RefKind.None, explicitInterfaceImplementations: default, - name: GetMetadataName(operatorKind), + GetMetadataName(operatorKind), typeParameters: ImmutableArray.Empty, - parameters: parameters, - returnTypeAttributes: returnTypeAttributes) + parameters, + returnTypeAttributes, + documentationCommentXml) { } diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationSymbol.cs index d0084ef2d650a..4822004d4d5eb 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationSymbol.cs @@ -16,10 +16,10 @@ namespace Microsoft.CodeAnalysis.CodeGeneration { internal abstract class CodeGenerationSymbol : ISymbol { - protected static ConditionalWeakTable annotationsTable = - new(); + protected static ConditionalWeakTable annotationsTable = new(); private ImmutableArray _attributes; + protected readonly string _documentationCommentXml; public Accessibility DeclaredAccessibility { get; } protected internal DeclarationModifiers Modifiers { get; } @@ -32,7 +32,8 @@ protected CodeGenerationSymbol( ImmutableArray attributes, Accessibility declaredAccessibility, DeclarationModifiers modifiers, - string name) + string name, + string documentationCommentXml = null) { this.ContainingAssembly = containingAssembly; this.ContainingType = containingType; @@ -40,6 +41,7 @@ protected CodeGenerationSymbol( this.DeclaredAccessibility = declaredAccessibility; this.Modifiers = modifiers; this.Name = name; + _documentationCommentXml = documentationCommentXml; } protected abstract CodeGenerationSymbol Clone(); @@ -183,7 +185,7 @@ public string GetDocumentationCommentXml( bool expandIncludes, CancellationToken cancellationToken) { - return ""; + return _documentationCommentXml ?? ""; } public string ToDisplayString(SymbolDisplayFormat format = null) diff --git a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationService.cs b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationService.cs index 8296d333c4096..63f956ffd76e0 100644 --- a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationService.cs +++ b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationService.cs @@ -9,7 +9,6 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; @@ -26,19 +25,24 @@ protected abstract TSyntaxContext CreateContext( protected abstract AbstractRecommendationServiceRunner CreateRunner( TSyntaxContext context, bool filterOutOfScopeLocals, CancellationToken cancellationToken); - public Task> GetRecommendedSymbolsAtPositionAsync( - Workspace workspace, SemanticModel semanticModel, int position, OptionSet options, CancellationToken cancellationToken) + public RecommendedSymbols GetRecommendedSymbolsAtPosition(Workspace workspace, SemanticModel semanticModel, int position, OptionSet options, CancellationToken cancellationToken) { var context = CreateContext(workspace, semanticModel, position, cancellationToken); var filterOutOfScopeLocals = options.GetOption(RecommendationOptions.FilterOutOfScopeLocals, semanticModel.Language); - var symbols = CreateRunner(context, filterOutOfScopeLocals, cancellationToken).GetSymbols(); + var result = CreateRunner(context, filterOutOfScopeLocals, cancellationToken).GetRecommendedSymbols(); + + var namedSymbols = result.NamedSymbols; + var unnamedSymbols = result.UnnamedSymbols; var hideAdvancedMembers = options.GetOption(RecommendationOptions.HideAdvancedMembers, semanticModel.Language); - symbols = symbols.FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, semanticModel.Compilation); + namedSymbols = namedSymbols.FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, semanticModel.Compilation); + unnamedSymbols = unnamedSymbols.FilterToVisibleAndBrowsableSymbols(hideAdvancedMembers, semanticModel.Compilation); var shouldIncludeSymbolContext = new ShouldIncludeSymbolContext(context, cancellationToken); - symbols = symbols.WhereAsArray(shouldIncludeSymbolContext.ShouldIncludeSymbol); - return Task.FromResult(symbols); + namedSymbols = namedSymbols.WhereAsArray(shouldIncludeSymbolContext.ShouldIncludeSymbol); + unnamedSymbols = unnamedSymbols.WhereAsArray(shouldIncludeSymbolContext.ShouldIncludeSymbol); + + return new RecommendedSymbols(namedSymbols, unnamedSymbols); } private sealed class ShouldIncludeSymbolContext diff --git a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs index 0f99e0b1b24de..c984ef25b7ff3 100644 --- a/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs +++ b/src/Workspaces/Core/Portable/Recommendations/AbstractRecommendationServiceRunner.cs @@ -37,7 +37,7 @@ public AbstractRecommendationServiceRunner( _cancellationToken = cancellationToken; } - public abstract ImmutableArray GetSymbols(); + public abstract RecommendedSymbols GetRecommendedSymbols(); public abstract bool TryGetExplicitTypeOfLambdaParameter(SyntaxNode lambdaSyntax, int ordinalInLambda, [NotNullWhen(returnValue: true)] out ITypeSymbol explicitLambdaParameterType); diff --git a/src/Workspaces/Core/Portable/Recommendations/IRecommendationService.cs b/src/Workspaces/Core/Portable/Recommendations/IRecommendationService.cs index e280ec895f7d0..3afee01ebb4fc 100644 --- a/src/Workspaces/Core/Portable/Recommendations/IRecommendationService.cs +++ b/src/Workspaces/Core/Portable/Recommendations/IRecommendationService.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; @@ -14,11 +11,40 @@ namespace Microsoft.CodeAnalysis.Recommendations { internal interface IRecommendationService : ILanguageService { - Task> GetRecommendedSymbolsAtPositionAsync( + RecommendedSymbols GetRecommendedSymbolsAtPosition( Workspace workspace, SemanticModel semanticModel, int position, OptionSet options, CancellationToken cancellationToken); } + + internal readonly struct RecommendedSymbols + { + private readonly ImmutableArray _namedSymbols; + private readonly ImmutableArray _unnamedSymbols; + + /// + /// The named symbols to recommend. + /// + public ImmutableArray NamedSymbols => _namedSymbols.NullToEmpty(); + + /// + /// The unnamed symbols to recommend. For example, operators, conversions and indexers. + /// + public ImmutableArray UnnamedSymbols => _unnamedSymbols.NullToEmpty(); + + public RecommendedSymbols(ImmutableArray namedSymbols) + : this(namedSymbols, default) + { + } + + public RecommendedSymbols( + ImmutableArray namedSymbols, + ImmutableArray unnamedSymbols = default) + { + _namedSymbols = namedSymbols; + _unnamedSymbols = unnamedSymbols; + } + } } diff --git a/src/Workspaces/Core/Portable/Recommendations/Recommender.cs b/src/Workspaces/Core/Portable/Recommendations/Recommender.cs index 3598ea76d81a0..b71b01337838c 100644 --- a/src/Workspaces/Core/Portable/Recommendations/Recommender.cs +++ b/src/Workspaces/Core/Portable/Recommendations/Recommender.cs @@ -2,57 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Recommendations { public static class Recommender { - /// - /// Obsolete. Use . - /// - [Obsolete("Use GetRecommendedSymbolsAtPositionAsync instead.")] public static IEnumerable GetRecommendedSymbolsAtPosition( SemanticModel semanticModel, int position, Workspace workspace, - OptionSet options = null, + OptionSet? options = null, CancellationToken cancellationToken = default) { - return GetRecommendedSymbolsAtPositionAsync(semanticModel, position, workspace, options, cancellationToken).WaitAndGetResult(cancellationToken); - } + options ??= workspace.Options; + var languageRecommender = workspace.Services.GetLanguageServices(semanticModel.Language).GetRequiredService(); - public static async Task> GetRecommendedSymbolsAtPositionAsync( - SemanticModel semanticModel, - int position, - Workspace workspace, - OptionSet options = null, - CancellationToken cancellationToken = default) - { - return await GetImmutableRecommendedSymbolsAtPositionAsync( - semanticModel, position, workspace, options, cancellationToken).ConfigureAwait(false); + return languageRecommender.GetRecommendedSymbolsAtPosition(workspace, semanticModel, position, options, cancellationToken).NamedSymbols; } - internal static async Task> GetImmutableRecommendedSymbolsAtPositionAsync( - SemanticModel semanticModel, - int position, - Workspace workspace, - OptionSet options = null, - CancellationToken cancellationToken = default) + public static Task> GetRecommendedSymbolsAtPositionAsync( + SemanticModel semanticModel, + int position, + Workspace workspace, + OptionSet? options = null, + CancellationToken cancellationToken = default) { - options ??= workspace.Options; - var languageRecommender = workspace.Services.GetLanguageServices(semanticModel.Language).GetService(); - - return await languageRecommender.GetRecommendedSymbolsAtPositionAsync( - workspace, semanticModel, position, options, cancellationToken).ConfigureAwait(false); + return Task.FromResult(GetRecommendedSymbolsAtPosition(semanticModel, position, workspace, options, cancellationToken)); } } } diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.resx b/src/Workspaces/Core/Portable/WorkspacesResources.resx index 00bc428ca8a14..06deb5218f886 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.resx +++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx @@ -573,4 +573,7 @@ CodeAction '{0}' did not produce a changed solution "CodeAction" is a specific type, and {0} represents the title shown by the action. + + Predefined conversion from {0} to {1}. + \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 4fc8f4a21d19c..46840d9828795 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -87,6 +87,11 @@ Možnosti nepocházely ze zadaného řešení. + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Projekt neobsahuje zadaný odkaz. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index 72e0005f07a29..ae53b07fff3d4 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -87,6 +87,11 @@ Optionen stammen nicht aus der angegebenen Projektmappe. + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Der angegebene Verweis ist im Projekt nicht enthalten. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index a2801fa916b8a..bd08dd57c9822 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -87,6 +87,11 @@ Las opciones no procedían de la solución especificada. + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference El proyecto no contiene la referencia especificada. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 78662bc24181b..5cff98addf71d 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -87,6 +87,11 @@ Les options ne proviennent pas de la solution spécifiée + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Le projet ne contient pas la référence spécifiée diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index 71ed24579c257..7652ba9665636 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -87,6 +87,11 @@ Le opzioni non provengono dalla soluzione specificata + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Il progetto non contiene il riferimento specificato diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index bd554d3c9c74b..06f41b694c916 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -87,6 +87,11 @@ オプションは指定されたソリューションに由来するものではありません + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference 指定された参照がプロジェクトに含まれていません diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index 4a85c161af349..cd3e830449a9a 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -87,6 +87,11 @@ 옵션을 지정된 솔루션에서 가져오지 않음 + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference 지정된 참조가 프로젝트에 포함되어 있지 않습니다. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index 7e754b93a2021..483af35ac98b0 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -87,6 +87,11 @@ Opcje nie pochodziły z określonego rozwiązania + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Projekt nie zawiera określonego odwołania diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index 9d4c30ed6c513..b7268419f091e 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -87,6 +87,11 @@ As opções não vieram da Solução especificada + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference O projeto não contém a referência especificada diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index b44bf058525f8..f879d50f33094 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -87,6 +87,11 @@ Параметры получены не из указанного решения. + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Проект не содержит указанную ссылку. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index 4a5b895abda56..22549d4d09807 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -87,6 +87,11 @@ Seçenekler belirtilen Çözümden gelmedi + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference Proje belirtilen başvuruyu içermiyor diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index fb282c3ed7c8d..46b16d8017f24 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -87,6 +87,11 @@ 选项并非来自指定的解决方案 + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference 项目不包含指定的引用 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index 4fcffc99d9f72..2e3d38345c237 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -87,6 +87,11 @@ 選項並非來自指定的解決方案 + + Predefined conversion from {0} to {1}. + Predefined conversion from {0} to {1}. + + Project does not contain specified reference 專案未包含指定的參考 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs index 62c5d116b5295..aa7e4fe88ff25 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ITypeSymbolExtensions.cs @@ -54,6 +54,14 @@ public static bool IsSystemVoid([NotNullWhen(returnValue: true)] this ITypeSymbo public static bool IsNullable([NotNullWhen(returnValue: true)] this ITypeSymbol? symbol) => symbol?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + public static bool IsNonNullableValueType([NotNullWhen(returnValue: true)] this ITypeSymbol? symbol) + { + if (symbol?.IsValueType != true) + return false; + + return !symbol.IsNullable(); + } + public static bool IsNullable( [NotNullWhen(true)] this ITypeSymbol? symbol, [NotNullWhen(true)] out ITypeSymbol? underlyingType) diff --git a/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb b/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb index 0e5e96b3cd467..2ca6c7436ee81 100644 --- a/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb +++ b/src/Workspaces/VisualBasic/Portable/Recommendations/VisualBasicRecommendationServiceRunner.vb @@ -18,7 +18,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Recommendations MyBase.New(context, filterOutOfScopeLocals, cancellationToken) End Sub - Public Overrides Function GetSymbols() As ImmutableArray(Of ISymbol) + Public Overrides Function GetRecommendedSymbols() As RecommendedSymbols + Return New RecommendedSymbols(GetSymbols()) + End Function + + Private Overloads Function GetSymbols() As ImmutableArray(Of ISymbol) If _context.SyntaxTree.IsInNonUserCode(_context.Position, _cancellationToken) OrElse _context.SyntaxTree.IsInSkippedText(_context.Position, _cancellationToken) Then Return ImmutableArray(Of ISymbol).Empty