From df75a08ed40f2acbc8971ca5b38d7cefe4c0d8c9 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 25 Mar 2021 21:56:07 -0700 Subject: [PATCH 1/6] Show static members of matching types in completion Closes #28603 --- ...ompletionListTagCompletionProviderTests.cs | 1082 ++++++++++++----- ...mAndCompletionListTagCompletionProvider.cs | 112 +- 2 files changed, 852 insertions(+), 342 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index e6baa8ab2e00..6582a0e90a2b 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -29,7 +29,8 @@ static void Main(string[] args) Colors c = Colors.Blue; } } - +"; + var colors = @" enum Colors { Red, @@ -37,7 +38,17 @@ enum Colors Green, } "; - await VerifyItemExistsAsync(markup, "Colors"); + var colorsLike = @" +readonly struct Colors +{ + public static readonly Colors Red; + public static readonly Colors Blue; + public static readonly Colors Green; +} +"; + + await VerifyItemExistsAsync(markup + colors, "Colors"); + await VerifyItemExistsAsync(markup + colorsLike, "Colors"); } [Fact] @@ -60,6 +71,13 @@ public enum Goo { Member }"; + var referencedCode_EnumLike = @" +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Always)] +public readonly struct Goo +{ + public static readonly Goo Member; +}"; + await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -68,6 +86,15 @@ await VerifyItemInEditorBrowsableContextsAsync( expectedSymbolsMetadataReference: 1, sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "Goo", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); } [Fact] @@ -90,6 +117,13 @@ public enum Goo { Member }"; + var referencedCode_EnumLike = @" +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] +public readonly struct Goo +{ + public static readonly Goo Member; +}"; + await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -98,6 +132,15 @@ await VerifyItemInEditorBrowsableContextsAsync( expectedSymbolsMetadataReference: 0, sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "Goo", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); } [Fact] @@ -120,6 +163,13 @@ public enum Goo { Member }"; + var referencedCode_EnumLike = @" +[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] +public enum Goo +{ + Member +}"; + await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -139,6 +189,26 @@ await VerifyItemInEditorBrowsableContextsAsync( sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp, hideAdvancedMembers: false); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "Goo", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: true); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "Goo", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: false); } [WorkItem(854099, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/854099")] @@ -151,8 +221,8 @@ static void Main(string[] args) { Colors c = // $$ } -} - +"; + var colors = @" enum Colors { Red, @@ -160,243 +230,281 @@ enum Colors Green, } "; - await VerifyNoItemsExistAsync(markup); + var colorsLike = @" +readonly struct Colors +{ + public static readonly Colors Red; + public static readonly Colors Blue; + public static readonly Colors Green; +} +"; + + await VerifyNoItemsExistAsync(markup + colors); + await VerifyNoItemsExistAsync(markup + colorsLike); } [WorkItem(827897, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/827897")] - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InYieldReturnInMethod() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InYieldReturnInMethod(string typeName) { var markup = -@"using System; +$@"using System; using System.Collections.Generic; class Program -{ - IEnumerable M() - { +{{ + IEnumerable<{typeName}> M() + {{ yield return $$ - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } [WorkItem(30235, "https://github.com/dotnet/roslyn/issues/30235")] - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InYieldReturnInLocalFunction() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InYieldReturnInLocalFunction(string typeName) { var markup = -@"using System; +$@"using System; using System.Collections.Generic; class Program -{ +{{ void M() - { - IEnumerable F() - { + {{ + IEnumerable<{typeName}> F() + {{ yield return $$ - } - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } [WorkItem(827897, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/827897")] - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InAsyncMethodReturnStatement() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InAsyncMethodReturnStatement(string typeName) { var markup = -@"using System; +$@"using System; using System.Threading.Tasks; class Program -{ - async Task M() - { +{{ + async Task<{typeName}> M() + {{ await Task.Delay(1); return $$ - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InSimpleLambdaAfterArrow() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InSimpleLambdaAfterArrow(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { +{{ + Func M() + {{ return _ => $$ - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InParenthesizedLambdaAfterArrow() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InParenthesizedLambdaAfterArrow(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { +{{ + Func<{typeName}> M() + {{ return () => $$ - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInAnonymousMethodAfterParameterList() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInAnonymousMethodAfterParameterList(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { +{{ + Func<{typeName}> M() + {{ return delegate () $$ - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInSimpleLambdaAfterAsync() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInSimpleLambdaAfterAsync(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { +{{ + Func M() + {{ return async $$ _ => - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInParenthesizedLambdaAfterAsync() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInParenthesizedLambdaAfterAsync(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { +{{ + Func<{typeName}> M() + {{ return async $$ () => - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInAnonymousMethodAfterAsync() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInAnonymousMethodAfterAsync(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { +{{ + Func<{typeName}> M() + {{ return async $$ delegate () - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInSimpleLambdaBlock() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInSimpleLambdaBlock(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { - return _ => { $$ } - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); +{{ + Func M() + {{ + return _ => {{ $$ }} + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInParenthesizedLambdaBlock() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInParenthesizedLambdaBlock(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { - return () => { $$ } - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); +{{ + Func<{typeName}> M() + {{ + return () => {{ $$ }} + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task NotInAnonymousMethodBlock() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task NotInAnonymousMethodBlock(string typeName) { var markup = -@"using System; +$@"using System; class Program -{ - Func M() - { - return delegate () { $$ } - } -}"; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek"); +{{ + Func<{typeName}> M() + {{ + return delegate () {{ $$ }} + }} +}}"; + await VerifyItemIsAbsentAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InExpressionTreeSimpleLambdaAfterArrow() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InExpressionTreeSimpleLambdaAfterArrow(string typeName) { var markup = -@"using System; +$@"using System; using System.Linq.Expressions; class Program -{ - Expression> M() - { +{{ + Expression> M() + {{ return _ => $$ - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InExpressionTreeParenthesizedLambdaAfterArrow() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task InExpressionTreeParenthesizedLambdaAfterArrow(string typeName) { var markup = -@"using System; +$@"using System; using System.Linq.Expressions; class Program -{ - Expression> M() - { +{{ + Expression> M() + {{ return () => $$ - } -}"; - await VerifyItemExistsAsync(markup, "DayOfWeek"); + }} +}}"; + await VerifyItemExistsAsync(markup, typeName); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -541,63 +649,69 @@ void Goo() } [WorkItem(828196, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/828196")] - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task SuggestAlias() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("System.Globalization.DigitShapes")] + [InlineData("System.DateTime")] + public async Task SuggestAlias(string fullTypeName) { - var markup = @" -using D = System.Globalization.DigitShapes; + var markup = $@" +using D = {fullTypeName}; class Program -{ +{{ static void Main(string[] args) - { + {{ D d= $$ - } -}"; + }} +}}"; await VerifyItemExistsAsync(markup, "D"); } [WorkItem(828196, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/828196")] - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task SuggestAlias2() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("System.Globalization.DigitShapes")] + [InlineData("System.DateTime")] + public async Task SuggestAlias2(string fullTypeName) { - var markup = @" + var markup = $@" namespace N -{ -using D = System.Globalization.DigitShapes; +{{ +using D = {fullTypeName}; class Program -{ +{{ static void Main(string[] args) - { + {{ D d= $$ - } -} -} + }} +}} +}} "; await VerifyItemExistsAsync(markup, "D"); } [WorkItem(828196, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/828196")] - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task SuggestAlias3() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("System.Globalization.DigitShapes")] + [InlineData("System.DateTime")] + public async Task SuggestAlias3(string fullTypeName) { - var markup = @" + var markup = $@" namespace N -{ -using D = System.Globalization.DigitShapes; +{{ +using D = {fullTypeName}; class Program -{ - private void Goo(System.Globalization.DigitShapes shape) - { - } +{{ + private void Goo({fullTypeName} shape) + {{ + }} static void Main(string[] args) - { + {{ Goo($$ - } -} -} + }} +}} +}} "; await VerifyItemExistsAsync(markup, "D"); } @@ -606,12 +720,19 @@ static void Main(string[] args) [Fact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task NotInParameterNameContext() { - var markup = @" + var enumE = @" enum E { a } - +"; + var enumLikeE = @" +readonly struct E +{ + public static readonly E a; +} +"; + var markup = @" class C { void goo(E first, E second) @@ -620,7 +741,10 @@ void goo(E first, E second) } } "; - await VerifyItemIsAbsentAsync(markup, "E"); + + await VerifyItemIsAbsentAsync(enumE + markup, "E"); + + await VerifyItemIsAbsentAsync(enumLikeE + markup, "E"); } [WorkItem(4310, "https://github.com/dotnet/roslyn/issues/4310")] @@ -632,7 +756,8 @@ public async Task InExpressionBodiedProperty() { Colors Colors => $$ } - +"; + var colors = @" enum Colors { Red, @@ -640,7 +765,17 @@ enum Colors Green, } "; - await VerifyItemExistsAsync(markup, "Colors"); + var colorsLike = @" +readonly struct Colors +{ + public static readonly Colors Red; + public static readonly Colors Blue; + public static readonly Colors Green; +} +"; + + await VerifyItemExistsAsync(markup + colors, "Colors"); + await VerifyItemExistsAsync(markup + colorsLike, "Colors"); } [WorkItem(4310, "https://github.com/dotnet/roslyn/issues/4310")] @@ -652,7 +787,8 @@ public async Task InExpressionBodiedMethod() { Colors GetColors() => $$ } - +"; + var colors = @" enum Colors { Red, @@ -660,7 +796,17 @@ enum Colors Green, } "; - await VerifyItemExistsAsync(markup, "Colors"); + var colorsLike = @" +readonly struct Colors +{ + public static readonly Colors Red; + public static readonly Colors Blue; + public static readonly Colors Green; +} +"; + + await VerifyItemExistsAsync(markup + colors, "Colors"); + await VerifyItemExistsAsync(markup + colorsLike, "Colors"); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -678,16 +824,26 @@ static void Main(string[] args) static void M(E e) { } } - - enum E - { - A, - B, - } } "; - await VerifyNoItemsExistAsync(markup); - } + var enumE = @" +enum E +{ + A, + B, +} +"; + var enumLikeE = @" +readonly struct E +{ + public static readonly E A; + public static readonly E B; +} +"; + + await VerifyNoItemsExistAsync(markup + enumE); + await VerifyNoItemsExistAsync(markup + enumLikeE); + } [WorkItem(18359, "https://github.com/dotnet/roslyn/issues/18359")] [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -705,15 +861,25 @@ static void Main(string[] args) static void M(E e) { } } - - enum E - { - A, - B, - } } "; - await VerifyNoItemsExistAsync(markup); + var enumE = @" +enum E +{ + A, + B, +} +"; + var enumLikeE = @" +readonly struct E +{ + public static readonly E A; + public static readonly E B; +} +"; + + await VerifyNoItemsExistAsync(markup + enumE); + await VerifyNoItemsExistAsync(markup + enumLikeE); } [WorkItem(5419, "https://github.com/dotnet/roslyn/issues/5419")] @@ -853,6 +1019,12 @@ public enum MyEnum [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Always)] Member }"; + var referencedCode_EnumLike = @" +public readonly struct MyEnum +{ + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Always)] + public static readonly MyEnum Member; +}"; await VerifyItemInEditorBrowsableContextsAsync( markup: markup, @@ -862,6 +1034,15 @@ await VerifyItemInEditorBrowsableContextsAsync( expectedSymbolsMetadataReference: 1, sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "MyEnum.Member", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); } [Fact] @@ -882,6 +1063,13 @@ public enum MyEnum [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] Member }"; + var referencedCode_EnumLike = @" +public readonly struct MyEnum +{ + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public static readonly MyEnum Member; +}"; + await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -890,6 +1078,15 @@ await VerifyItemInEditorBrowsableContextsAsync( expectedSymbolsMetadataReference: 0, sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "MyEnum.Member", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp); } [Fact] @@ -910,6 +1107,13 @@ public enum MyEnum [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] Member }"; + var referencedCode_EnumLike = @" +public readonly struct MyEnum +{ + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] + public static readonly MyEnum Member; +}"; + await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -929,6 +1133,26 @@ await VerifyItemInEditorBrowsableContextsAsync( sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp, hideAdvancedMembers: false); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "MyEnum.Member", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 0, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: true); + + await VerifyItemInEditorBrowsableContextsAsync( + markup: markup, + referencedCode: referencedCode_EnumLike, + item: "MyEnum.Member", + expectedSymbolsSameSolution: 1, + expectedSymbolsMetadataReference: 1, + sourceLanguage: LanguageNames.CSharp, + referencedLanguage: LanguageNames.CSharp, + hideAdvancedMembers: false); } [Fact] @@ -948,7 +1172,8 @@ public static void Bar(Goo f) { } } - +"; + var goo = @" enum Goo { AMember, @@ -956,7 +1181,17 @@ enum Goo CMember } "; - await VerifyItemExistsAsync(markup, "Goo.AMember", usePreviousCharAsTrigger: true); + var gooLike = @" +readonly struct Goo +{ + public static readonly Goo AMember; + public static readonly Goo BMember; + public static readonly Goo CMember; +} +"; + + await VerifyItemExistsAsync(markup + goo, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: true); } [Fact] @@ -972,16 +1207,26 @@ public static void Main(string[] args) x = $$; } } - +"; + var goo = @" enum Goo { AMember, BMember, CMember } +"; + var gooLike = @" +readonly struct Goo +{ + public static readonly Goo AMember; + public static readonly Goo BMember; + public static readonly Goo CMember; +} "; - await VerifyItemExistsAsync(markup, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + goo, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: true); } [Fact] @@ -989,13 +1234,6 @@ enum Goo public async Task TestCaseStatement() { var markup = @" -enum E -{ - A, - B, - C -} - static class Module1 { public static void Main(string[] args) @@ -1008,152 +1246,195 @@ public static void Main(string[] args) } } } +"; + var e = @" +enum E +{ + A, + B, + C +} +"; + var eLike = @" +readonly struct E +{ + public static readonly E A; + public static readonly E B; + public static readonly E C; +} "; - await VerifyItemExistsAsync(markup, "E.A", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(e + markup, "E.A", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(eLike + markup, "E.A", usePreviousCharAsTrigger: true); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestInYieldReturn() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestInYieldReturn(string typeName, string memberName) { - var markup = @" + var markup = $@" using System; using System.Collections.Generic; class C -{ - public IEnumerable M() - { +{{ + public IEnumerable<{typeName}> M() + {{ yield return $$; - } -} + }} +}} "; - await VerifyItemExistsAsync(markup, "DayOfWeek.Friday"); + await VerifyItemExistsAsync(markup, $"{typeName}.{memberName}"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestInAsyncMethodReturnStatement() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestInAsyncMethodReturnStatement(string typeName, string memberName) { - var markup = @" + var markup = $@" using System; using System.Threading.Tasks; class C -{ - public async Task M() - { +{{ + public async Task<{typeName}> M() + {{ await Task.Delay(1); return $$; - } -} + }} +}} "; - await VerifyItemExistsAsync(markup, "DayOfWeek.Friday"); + await VerifyItemExistsAsync(markup, $"{typeName}.{memberName}"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestInIndexedProperty() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestInIndexedProperty(string typeName, string memberName) { - var markup = @" + var markup = $@" +using System; static class Module1 -{ - public enum MyEnum - { - flower - } - +{{ public class MyClass1 - { - public bool this[MyEnum index] - { + {{ + public bool this[{typeName} index] + {{ set - { - } - } - } + {{ + }} + }} + }} public static void Main() - { + {{ var c = new MyClass1(); - c[$$MyEnum.flower] = true; - } -} + c[$${typeName}.{memberName}] = true; + }} +}} "; - await VerifyItemExistsAsync(markup, "MyEnum.flower"); + await VerifyItemExistsAsync(markup, $"{typeName}.{memberName}"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestFullyQualified() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestFullyQualified(string typeName, string memberName) { - var markup = @" + var markup = $@" class C -{ - public void M(System.DayOfWeek day) - { +{{ + public void M(System.{typeName} day) + {{ M($$); - } + }} enum DayOfWeek - { + {{ A, B - } -} + }} + + struct DateTime + {{ + public static readonly DateTime A; + public static readonly DateTime B; + }} +}} "; - await VerifyItemExistsAsync(markup, "System.DayOfWeek.Friday"); + await VerifyItemExistsAsync(markup, $"System.{typeName}.{memberName}"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestTriggeredForNamedArgument() + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task TestTriggeredForNamedArgument(string typeName) { - var markup = @" + var markup = $@" class C -{ - public void M(DayOfWeek day) - { +{{ + public void M({typeName} day) + {{ M(day: $$); - } + }} enum DayOfWeek - { + {{ A, B - } -} + }} + + struct DateTime + {{ + public static readonly DateTime A; + public static readonly DateTime B; + }} +}} "; - await VerifyItemExistsAsync(markup, "DayOfWeek.A", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup, $"{typeName}.A", usePreviousCharAsTrigger: true); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestNotTriggeredAfterAssignmentEquals() + [InlineData(nameof(DayOfWeek))] + [InlineData(nameof(DateTime))] + public async Task TestNotTriggeredAfterAssignmentEquals(string typeName) { - var markup = @" + var markup = $@" class C -{ - public void M(DayOfWeek day) - { +{{ + public void M({typeName} day) + {{ var x = $$; - } + }} enum DayOfWeek - { + {{ A, B - } -} + }} + + struct DateTime + {{ + public static readonly DateTime A; + public static readonly DateTime B; + }} +}} "; - await VerifyItemIsAbsentAsync(markup, "DayOfWeek.A", usePreviousCharAsTrigger: true); + await VerifyItemIsAbsentAsync(markup, $"{typeName}.A", usePreviousCharAsTrigger: true); } [Fact] @@ -1296,70 +1577,76 @@ public void M() await VerifyProviderCommitAsync(markup, "E.A", expected, ';'); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task EnumMember_NotAfterDot() + [InlineData(nameof(ConsoleKey))] + [InlineData(nameof(DateTime))] + public async Task EnumMember_NotAfterDot(string typeName) { - var markup = @" + var markup = $@" static class Module1 -{ - public static void Main() - { - while (System.Console.ReadKey().Key == System.ConsoleKey.$$ - { - } - } -} +{{ + public static void Main({typeName} x) + {{ + while (x == System.{typeName}.$$ + {{ + }} + }} +}} "; await VerifyNoItemsExistAsync(markup); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestInCollectionInitializer1() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Monday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestInCollectionInitializer1(string typeName, string memberName) { - var markup = @" + var markup = $@" using System; using System.Collections.Generic; class C -{ +{{ public void Main() - { - var y = new List() - { + {{ + var y = new List<{typeName}>() + {{ $$ - }; - } -} + }}; + }} +}} "; - await VerifyItemExistsAsync(markup, "DayOfWeek.Monday"); + await VerifyItemExistsAsync(markup, $"{typeName}.{memberName}"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestInCollectionInitializer2() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Monday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestInCollectionInitializer2(string typeName, string memberName) { - var markup = @" + var markup = $@" using System; using System.Collections.Generic; class C -{ +{{ public void Main() - { - var y = new List() - { - DayOfWeek.Monday, + {{ + var y = new List<{typeName}>() + {{ + {typeName}.{memberName}, $$ - }; - } -} + }}; + }} +}} "; - await VerifyItemExistsAsync(markup, "DayOfWeek.Monday"); + await VerifyItemExistsAsync(markup, $"{typeName}.{memberName}"); } [Fact] @@ -1401,8 +1688,22 @@ public enum Palette AccentColor2, } + public readonly struct ColorLike + { + public static readonly ColorLike Red; + public static readonly ColorLike Green; + } + + public readonly struct PaletteLike + { + public static readonly PaletteLike AccentColor1; + public static readonly PaletteLike AccentColor2; + } + public void SetColor(Color color) { } public void SetColor(Palette palette) { } + public void SetColor(ColorLike color) { } + public void SetColor(PaletteLike palette) { } public void Main() { @@ -1410,51 +1711,54 @@ public void Main() } } "; + await VerifyItemExistsAsync(markup, "Color.Red"); await VerifyItemExistsAsync(markup, "Palette.AccentColor1"); + + await VerifyItemExistsAsync(markup, "ColorLike.Red"); + await VerifyItemExistsAsync(markup, "PaletteLike.AccentColor1"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestNullableEnum() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestNullableEnum(string typeName, string memberName) { - var markup = @" + var markup = $@" +using System; class C -{ - public enum Color - { - Red, - Green, - } - - public void SetColor(Color? color) { } +{{ + public void SetValue({typeName}? value) {{ }} public void Main() - { - SetColor($$ - } -} + {{ + SetValue($$ + }} +}} "; - await VerifyItemExistsAsync(markup, "Color.Red"); + await VerifyItemExistsAsync(markup, $"{typeName}.{memberName}"); } - [Fact] + [Theory] [Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestTypeAlias() + [InlineData(nameof(DayOfWeek), nameof(DayOfWeek.Friday))] + [InlineData(nameof(DateTime), nameof(DateTime.Now))] + public async Task TestTypeAlias(string typeName, string memberName) { - var markup = @" -using AT = System.AttributeTargets; + var markup = $@" +using AT = System.{typeName}; public class Program -{ - static void M(AT attributeTargets) { } +{{ + static void M(AT attributeTargets) {{ }} public static void Main() - { + {{ M($$ - } -}"; - await VerifyItemExistsAsync(markup, "AT.Assembly"); + }} +}}"; + await VerifyItemExistsAsync(markup, $"AT.{memberName}"); } [Theory] @@ -1604,6 +1908,118 @@ public void M() await VerifyItemExistsAsync(markup, "Color.Red"); } + [Fact] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestStaticAndInstanceMembers() + { + var markup = @" +public readonly struct Color +{ + public static readonly Color Red; + public readonly Color Green; +} + +class C +{ + public void M(Color color) + { + M($$ + } +} +"; + + await VerifyItemExistsAsync(markup, "Color.Red"); + await VerifyItemIsAbsentAsync(markup, "Color.Green"); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestProperties() + { + var markup = @" +public readonly struct Color +{ + public static Color Red { get; } + public Color Green { get; } +} + +class C +{ + public void M(Color color) + { + M($$ + } +} +"; + + await VerifyItemExistsAsync(markup, "Color.Red"); + await VerifyItemIsAbsentAsync(markup, "Color.Green"); + } + + [Theory] + [Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected internal")] + [InlineData("protected")] + [InlineData("private protected")] + [InlineData("private")] + public async Task TestAccessibilityDifferentType(string modifier) + { + var markup = $@" +public class Color +{{ + {modifier} static readonly Color Red; +}} + +class C +{{ + public void M(Color color) + {{ + M($$ + }} +}} +"; + + var expected = modifier switch + { + "public" => true, + "internal" => true, + "protected internal" => true, + _ => false, + }; + + if (expected) + await VerifyItemExistsAsync(markup, "Color.Red"); + else + await VerifyItemIsAbsentAsync(markup, "Color.Red"); + } + + [Theory] + [Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected internal")] + [InlineData("protected")] + [InlineData("private protected")] + [InlineData("private")] + public async Task TestAccessibilitySameType(string modifier) + { + var markup = $@" +public class Color +{{ + {modifier} static readonly Color Red; + + public void M(Color color) + {{ + M($$ + }} +}} +"; + + await VerifyItemExistsAsync(markup, "Color.Red"); + } + #endregion } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs index af1b2ea38b4e..0428c7a6d60c 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; @@ -114,14 +115,23 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman type = typeArg; } + var showTypeOnlyIfMembers = false; + INamedTypeSymbol? lazyEnclosingNamedType = null; var position = context.Position; if (type.TypeKind != TypeKind.Enum) { var enumType = TryGetEnumTypeInEnumInitializer(semanticModel, token, type, cancellationToken) ?? - TryGetCompletionListType(type, semanticModel.GetEnclosingNamedType(position, cancellationToken), semanticModel.Compilation); + TryGetCompletionListType(type, GetEnclosingNamedType(), semanticModel.Compilation); if (enumType == null) - return; + { + showTypeOnlyIfMembers = true; + enumType = TryGetTypeWithStaticMembers(type); + if (enumType == null) + { + return; + } + } type = enumType; } @@ -142,13 +152,11 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman var symbol = alias ?? type; var sortText = symbol.Name; - context.AddItem(SymbolCompletionItem.CreateWithSymbolId( - displayText, - displayTextSuffix: "", - symbols: ImmutableArray.Create(symbol), - rules: s_enumTypeRules, - contextPosition: position, - sortText: sortText)); + var addedType = false; + if (!showTypeOnlyIfMembers) + { + AddTypeIfNotAdded(); + } // And now all the accessible members of the enum. if (type.TypeKind == TypeKind.Enum) @@ -174,6 +182,84 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman filterText: memberDisplayName)); } } + else if (GetEnclosingNamedType() is { } enclosingNamedType) + { + // Build a list of the members with the same type as the target + foreach (var member in type.GetMembers()) + { + if (member is IFieldSymbol { IsStatic: true } field) + { + if (!SymbolEqualityComparer.Default.Equals(type, field.Type)) + continue; + + if (!field.IsAccessibleWithin(enclosingNamedType)) + continue; + + if (!field.IsEditorBrowsable(hideAdvancedMembers, semanticModel.Compilation)) + continue; + + AddTypeIfNotAdded(); + var memberDisplayName = $"{displayText}.{field.Name}"; + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText: memberDisplayName, + displayTextSuffix: "", + symbols: ImmutableArray.Create(field), + rules: CompletionItemRules.Default, + contextPosition: position, + sortText: memberDisplayName, + filterText: memberDisplayName)); + } + else if (member is IPropertySymbol { IsStatic: true, IsIndexer: false } property) + { + if (!SymbolEqualityComparer.Default.Equals(type, property.Type)) + continue; + + if (!property.IsAccessibleWithin(enclosingNamedType)) + continue; + + if (!property.IsEditorBrowsable(hideAdvancedMembers, semanticModel.Compilation)) + continue; + + AddTypeIfNotAdded(); + var memberDisplayName = $"{displayText}.{property.Name}"; + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText: memberDisplayName, + displayTextSuffix: "", + symbols: ImmutableArray.Create(property), + rules: CompletionItemRules.Default, + contextPosition: position, + sortText: memberDisplayName, + filterText: memberDisplayName)); + } + else + { + continue; + } + } + } + + return; + + // Local functions + INamedTypeSymbol? GetEnclosingNamedType() + { + return lazyEnclosingNamedType ??= semanticModel.GetEnclosingNamedType(position, cancellationToken); + } + + void AddTypeIfNotAdded() + { + if (addedType) + return; + + addedType = true; + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText, + displayTextSuffix: "", + symbols: ImmutableArray.Create(symbol), + rules: s_enumTypeRules, + contextPosition: position, + sortText: sortText)); + } } private static ITypeSymbol? TryGetEnumTypeInEnumInitializer( @@ -248,5 +334,13 @@ protected override Task GetDescriptionWorkerAsync(Documen ? completionListType : null; } + + private static INamedTypeSymbol? TryGetTypeWithStaticMembers(ITypeSymbol type) + { + if (type.TypeKind == TypeKind.Struct || type.TypeKind == TypeKind.Class) + return type as INamedTypeSymbol; + + return null; + } } } From 4713f3e666fb9c5d7cef9cdd3ea9561c83899063 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 26 Mar 2021 08:38:20 -0700 Subject: [PATCH 2/6] Simplify implementation for static member completion --- ...ompletionListTagCompletionProviderTests.cs | 125 ++++++++---------- ...mAndCompletionListTagCompletionProvider.cs | 110 ++++++--------- 2 files changed, 95 insertions(+), 140 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index 6582a0e90a2b..09d05ebdad13 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -48,7 +48,7 @@ readonly struct Colors "; await VerifyItemExistsAsync(markup + colors, "Colors"); - await VerifyItemExistsAsync(markup + colorsLike, "Colors"); + await VerifyItemIsAbsentAsync(markup + colorsLike, "Colors"); } [Fact] @@ -71,13 +71,6 @@ public enum Goo { Member }"; - var referencedCode_EnumLike = @" -[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Always)] -public readonly struct Goo -{ - public static readonly Goo Member; -}"; - await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -86,15 +79,6 @@ await VerifyItemInEditorBrowsableContextsAsync( expectedSymbolsMetadataReference: 1, sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); - - await VerifyItemInEditorBrowsableContextsAsync( - markup: markup, - referencedCode: referencedCode_EnumLike, - item: "Goo", - expectedSymbolsSameSolution: 1, - expectedSymbolsMetadataReference: 1, - sourceLanguage: LanguageNames.CSharp, - referencedLanguage: LanguageNames.CSharp); } [Fact] @@ -117,13 +101,6 @@ public enum Goo { Member }"; - var referencedCode_EnumLike = @" -[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] -public readonly struct Goo -{ - public static readonly Goo Member; -}"; - await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -132,15 +109,6 @@ await VerifyItemInEditorBrowsableContextsAsync( expectedSymbolsMetadataReference: 0, sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp); - - await VerifyItemInEditorBrowsableContextsAsync( - markup: markup, - referencedCode: referencedCode_EnumLike, - item: "Goo", - expectedSymbolsSameSolution: 1, - expectedSymbolsMetadataReference: 0, - sourceLanguage: LanguageNames.CSharp, - referencedLanguage: LanguageNames.CSharp); } [Fact] @@ -163,13 +131,6 @@ public enum Goo { Member }"; - var referencedCode_EnumLike = @" -[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] -public enum Goo -{ - Member -}"; - await VerifyItemInEditorBrowsableContextsAsync( markup: markup, referencedCode: referencedCode, @@ -189,26 +150,6 @@ await VerifyItemInEditorBrowsableContextsAsync( sourceLanguage: LanguageNames.CSharp, referencedLanguage: LanguageNames.CSharp, hideAdvancedMembers: false); - - await VerifyItemInEditorBrowsableContextsAsync( - markup: markup, - referencedCode: referencedCode_EnumLike, - item: "Goo", - expectedSymbolsSameSolution: 1, - expectedSymbolsMetadataReference: 0, - sourceLanguage: LanguageNames.CSharp, - referencedLanguage: LanguageNames.CSharp, - hideAdvancedMembers: true); - - await VerifyItemInEditorBrowsableContextsAsync( - markup: markup, - referencedCode: referencedCode_EnumLike, - item: "Goo", - expectedSymbolsSameSolution: 1, - expectedSymbolsMetadataReference: 1, - sourceLanguage: LanguageNames.CSharp, - referencedLanguage: LanguageNames.CSharp, - hideAdvancedMembers: false); } [WorkItem(854099, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/854099")] @@ -260,7 +201,11 @@ class Program yield return $$ }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [WorkItem(30235, "https://github.com/dotnet/roslyn/issues/30235")] @@ -283,7 +228,11 @@ void M() }} }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [WorkItem(827897, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/827897")] @@ -304,7 +253,11 @@ class Program return $$ }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [Theory, Trait(Traits.Feature, Traits.Features.Completion)] @@ -322,7 +275,11 @@ class Program return _ => $$ }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [Theory, Trait(Traits.Feature, Traits.Features.Completion)] @@ -340,7 +297,11 @@ class Program return () => $$ }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [Theory, Trait(Traits.Feature, Traits.Features.Completion)] @@ -485,7 +446,11 @@ class Program return _ => $$ }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [Theory, Trait(Traits.Feature, Traits.Features.Completion)] @@ -504,7 +469,11 @@ class Program return () => $$ }} }}"; - await VerifyItemExistsAsync(markup, typeName); + + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, typeName); + else + await VerifyItemIsAbsentAsync(markup, typeName); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -663,7 +632,11 @@ static void Main(string[] args) D d= $$ }} }}"; - await VerifyItemExistsAsync(markup, "D"); + + if (fullTypeName == "System.Globalization.DigitShapes") + await VerifyItemExistsAsync(markup, "D"); + else + await VerifyItemIsAbsentAsync(markup, "D"); } [WorkItem(828196, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/828196")] @@ -686,7 +659,11 @@ static void Main(string[] args) }} }} "; - await VerifyItemExistsAsync(markup, "D"); + + if (fullTypeName == "System.Globalization.DigitShapes") + await VerifyItemExistsAsync(markup, "D"); + else + await VerifyItemIsAbsentAsync(markup, "D"); } [WorkItem(828196, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/828196")] @@ -713,7 +690,11 @@ static void Main(string[] args) }} }} "; - await VerifyItemExistsAsync(markup, "D"); + + if (fullTypeName == "System.Globalization.DigitShapes") + await VerifyItemExistsAsync(markup, "D"); + else + await VerifyItemIsAbsentAsync(markup, "D"); } [WorkItem(828196, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/828196")] @@ -775,7 +756,7 @@ readonly struct Colors "; await VerifyItemExistsAsync(markup + colors, "Colors"); - await VerifyItemExistsAsync(markup + colorsLike, "Colors"); + await VerifyItemIsAbsentAsync(markup + colorsLike, "Colors"); } [WorkItem(4310, "https://github.com/dotnet/roslyn/issues/4310")] @@ -806,7 +787,7 @@ readonly struct Colors "; await VerifyItemExistsAsync(markup + colors, "Colors"); - await VerifyItemExistsAsync(markup + colorsLike, "Colors"); + await VerifyItemIsAbsentAsync(markup + colorsLike, "Colors"); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs index 0428c7a6d60c..188ca88c4c20 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs @@ -115,17 +115,23 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman type = typeArg; } - var showTypeOnlyIfMembers = false; - INamedTypeSymbol? lazyEnclosingNamedType = null; + // When true, this completion provider shows both the type (e.g. DayOfWeek) and its qualified members (e.g. + // DayOfWeek.Friday). We set this to false for enum-like cases (static members of structs and classes) so we + // only show the qualified members in these cases. + var showType = true; var position = context.Position; + var enclosingNamedType = semanticModel.GetEnclosingNamedType(position, cancellationToken); if (type.TypeKind != TypeKind.Enum) { var enumType = TryGetEnumTypeInEnumInitializer(semanticModel, token, type, cancellationToken) ?? - TryGetCompletionListType(type, GetEnclosingNamedType(), semanticModel.Compilation); + TryGetCompletionListType(type, enclosingNamedType, semanticModel.Compilation); if (enumType == null) { - showTypeOnlyIfMembers = true; + // If this isn't an enum or marked with completionlist, also check if it contains static members of + // a matching type. These 'enum-like' types have similar characteristics to enum completion, but do + // not show the containing type as a separate item in completion. + showType = false; enumType = TryGetTypeWithStaticMembers(type); if (enumType == null) { @@ -152,10 +158,15 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman var symbol = alias ?? type; var sortText = symbol.Name; - var addedType = false; - if (!showTypeOnlyIfMembers) + if (showType) { - AddTypeIfNotAdded(); + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText, + displayTextSuffix: "", + symbols: ImmutableArray.Create(symbol), + rules: s_enumTypeRules, + contextPosition: position, + sortText: sortText)); } // And now all the accessible members of the enum. @@ -182,83 +193,46 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman filterText: memberDisplayName)); } } - else if (GetEnclosingNamedType() is { } enclosingNamedType) + else if (enclosingNamedType is not null) { // Build a list of the members with the same type as the target foreach (var member in type.GetMembers()) { + ISymbol staticSymbol; + ITypeSymbol symbolType; if (member is IFieldSymbol { IsStatic: true } field) { - if (!SymbolEqualityComparer.Default.Equals(type, field.Type)) - continue; - - if (!field.IsAccessibleWithin(enclosingNamedType)) - continue; - - if (!field.IsEditorBrowsable(hideAdvancedMembers, semanticModel.Compilation)) - continue; - - AddTypeIfNotAdded(); - var memberDisplayName = $"{displayText}.{field.Name}"; - context.AddItem(SymbolCompletionItem.CreateWithSymbolId( - displayText: memberDisplayName, - displayTextSuffix: "", - symbols: ImmutableArray.Create(field), - rules: CompletionItemRules.Default, - contextPosition: position, - sortText: memberDisplayName, - filterText: memberDisplayName)); + staticSymbol = field; + symbolType = field.Type; } else if (member is IPropertySymbol { IsStatic: true, IsIndexer: false } property) { - if (!SymbolEqualityComparer.Default.Equals(type, property.Type)) - continue; - - if (!property.IsAccessibleWithin(enclosingNamedType)) - continue; - - if (!property.IsEditorBrowsable(hideAdvancedMembers, semanticModel.Compilation)) - continue; - - AddTypeIfNotAdded(); - var memberDisplayName = $"{displayText}.{property.Name}"; - context.AddItem(SymbolCompletionItem.CreateWithSymbolId( - displayText: memberDisplayName, - displayTextSuffix: "", - symbols: ImmutableArray.Create(property), - rules: CompletionItemRules.Default, - contextPosition: position, - sortText: memberDisplayName, - filterText: memberDisplayName)); + staticSymbol = property; + symbolType = property.Type; } else { + // Only fields and properties are supported for static member matching continue; } - } - } - - return; - - // Local functions - INamedTypeSymbol? GetEnclosingNamedType() - { - return lazyEnclosingNamedType ??= semanticModel.GetEnclosingNamedType(position, cancellationToken); - } - void AddTypeIfNotAdded() - { - if (addedType) - return; + if (!SymbolEqualityComparer.Default.Equals(type, symbolType) + || !staticSymbol.IsAccessibleWithin(enclosingNamedType) + || !staticSymbol.IsEditorBrowsable(hideAdvancedMembers, semanticModel.Compilation)) + { + continue; + } - addedType = true; - context.AddItem(SymbolCompletionItem.CreateWithSymbolId( - displayText, - displayTextSuffix: "", - symbols: ImmutableArray.Create(symbol), - rules: s_enumTypeRules, - contextPosition: position, - sortText: sortText)); + var memberDisplayName = $"{displayText}.{staticSymbol.Name}"; + context.AddItem(SymbolCompletionItem.CreateWithSymbolId( + displayText: memberDisplayName, + displayTextSuffix: "", + symbols: ImmutableArray.Create(staticSymbol), + rules: CompletionItemRules.Default, + contextPosition: position, + sortText: memberDisplayName, + filterText: memberDisplayName)); + } } } From 3475981a50c03c5bb060c4abcaa3518fdf6fbcfb Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 26 Mar 2021 08:58:21 -0700 Subject: [PATCH 3/6] Add test for enum-like type kinds --- ...ompletionListTagCompletionProviderTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index 09d05ebdad13..08554a739920 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -2001,6 +2001,31 @@ public void M(Color color) await VerifyItemExistsAsync(markup, "Color.Red"); } + [Theory] + [Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + public async Task TestEnumLikeTypeKinds(string typeKeyword) + { + var markup = $@" +public {typeKeyword} Color +{{ + public static readonly Color Red; +}} + +class C +{{ + public void M(Color color) + {{ + M($$ + }} +}} +"; + + await VerifyItemExistsAsync(markup, "Color.Red"); + } + #endregion } } From 6fbc6930b97d83988cc2859369417f639f2abe87 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 26 Mar 2021 10:49:04 -0700 Subject: [PATCH 4/6] Update tests to account for new behavior --- .../CSharpCompletionCommandHandlerTests.vb | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index c30665998ea7..b8986173b71d 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -1854,8 +1854,8 @@ public class C + + + Public Async Function TestImplicitObjectCreationExpression_WithSpace_EnumTypes(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , languageVersion:=LanguageVersion.CSharp9, showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendTypeChars(" ") + Await state.AssertSelectedCompletionItem(displayText:="C", isHardSelected:=True) + state.SendTypeChars("(") + + ' The use of enum types causes completion to trigger automatically, even when + ' showCompletionInArgumentLists is disabled. + Await state.AssertSignatureHelpSession() + + state.SendTypeChars("A") + Await state.AssertSelectedCompletionItem(displayText:="Alice:", isHardSelected:=True) + state.SendTypeChars(":") + Assert.Contains("new C(Alice:", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + End Using + End Function + + + + + Public Async Function TestImplicitObjectCreationExpression_WithSpace_EnumLikeTypes(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , languageVersion:=LanguageVersion.CSharp9, showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendTypeChars(" ") + Await state.AssertSelectedCompletionItem(displayText:="C", isHardSelected:=True) + state.SendTypeChars("(") + + ' The use of enum-like types (int.MaxValue and string.Empty) causes completion to trigger automatically, + ' even when showCompletionInArgumentLists is disabled. + Await state.AssertSignatureHelpSession() + + state.SendTypeChars("A") + Await state.AssertSelectedCompletionItem(displayText:="Alice:", isHardSelected:=True) + state.SendTypeChars(":") + Assert.Contains("new C(Alice:", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + End Using + End Function + @@ -3272,7 +3337,7 @@ class C } ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.SendTypeChars("var @this = ""goo""") + state.SendTypeChars("var @this = ""goo"";") state.SendReturn() state.SendTypeChars("string str = this.ToString();") state.SendReturn() From 0d2372ac37edcef173e01dbb917271115c19de59 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 26 Mar 2021 10:49:37 -0700 Subject: [PATCH 5/6] Enable tests that now pass Closes #47610 Closes #49861 --- .../Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index b8986173b71d..078b966b6edc 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -2635,7 +2635,7 @@ public class Goo End Function - + Public Async Function LocalFunctionAttributeNamedPropertyCompletionCommitWithTab(showCompletionInArgumentLists As Boolean) As Task Using state = TestStateFactory.CreateCSharpTestState( @@ -5342,7 +5342,7 @@ class C End Using End Function - + From b252b309320fb424308af7e8bc262b99565ef5c7 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 26 Mar 2021 13:43:10 -0700 Subject: [PATCH 6/6] Avoid automatically triggering completion for enum-like types --- ...ompletionListTagCompletionProviderTests.cs | 19 +++-- .../CSharpCompletionCommandHandlerTests.vb | 69 +------------------ ...mAndCompletionListTagCompletionProvider.cs | 11 ++- 3 files changed, 27 insertions(+), 72 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index 08554a739920..c3499e7c04e0 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -1172,7 +1172,9 @@ readonly struct Goo "; await VerifyItemExistsAsync(markup + goo, "Goo.AMember", usePreviousCharAsTrigger: true); - await VerifyItemExistsAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + goo, "Goo.AMember", usePreviousCharAsTrigger: false); + await VerifyItemIsAbsentAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: false); } [Fact] @@ -1207,7 +1209,9 @@ readonly struct Goo "; await VerifyItemExistsAsync(markup + goo, "Goo.AMember", usePreviousCharAsTrigger: true); - await VerifyItemExistsAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + goo, "Goo.AMember", usePreviousCharAsTrigger: false); + await VerifyItemIsAbsentAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(markup + gooLike, "Goo.AMember", usePreviousCharAsTrigger: false); } [Fact] @@ -1246,7 +1250,9 @@ readonly struct E "; await VerifyItemExistsAsync(e + markup, "E.A", usePreviousCharAsTrigger: true); - await VerifyItemExistsAsync(eLike + markup, "E.A", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(e + markup, "E.A", usePreviousCharAsTrigger: false); + await VerifyItemIsAbsentAsync(eLike + markup, "E.A", usePreviousCharAsTrigger: true); + await VerifyItemExistsAsync(eLike + markup, "E.A", usePreviousCharAsTrigger: false); } [Theory] @@ -1384,7 +1390,12 @@ struct DateTime }} "; - await VerifyItemExistsAsync(markup, $"{typeName}.A", usePreviousCharAsTrigger: true); + if (typeName == nameof(DayOfWeek)) + await VerifyItemExistsAsync(markup, $"{typeName}.A", usePreviousCharAsTrigger: true); + else + await VerifyItemIsAbsentAsync(markup, $"{typeName}.A", usePreviousCharAsTrigger: true); + + await VerifyItemExistsAsync(markup, $"{typeName}.A", usePreviousCharAsTrigger: false); } [Theory] diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 078b966b6edc..75a7d0edb236 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -1854,8 +1854,8 @@ public class C - - - Public Async Function TestImplicitObjectCreationExpression_WithSpace_EnumTypes(showCompletionInArgumentLists As Boolean) As Task - Using state = TestStateFactory.CreateCSharpTestState( - , languageVersion:=LanguageVersion.CSharp9, showCompletionInArgumentLists:=showCompletionInArgumentLists) - - state.SendTypeChars(" ") - Await state.AssertSelectedCompletionItem(displayText:="C", isHardSelected:=True) - state.SendTypeChars("(") - - ' The use of enum types causes completion to trigger automatically, even when - ' showCompletionInArgumentLists is disabled. - Await state.AssertSignatureHelpSession() - - state.SendTypeChars("A") - Await state.AssertSelectedCompletionItem(displayText:="Alice:", isHardSelected:=True) - state.SendTypeChars(":") - Assert.Contains("new C(Alice:", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) - End Using - End Function - - - - - Public Async Function TestImplicitObjectCreationExpression_WithSpace_EnumLikeTypes(showCompletionInArgumentLists As Boolean) As Task - Using state = TestStateFactory.CreateCSharpTestState( - , languageVersion:=LanguageVersion.CSharp9, showCompletionInArgumentLists:=showCompletionInArgumentLists) - - state.SendTypeChars(" ") - Await state.AssertSelectedCompletionItem(displayText:="C", isHardSelected:=True) - state.SendTypeChars("(") - - ' The use of enum-like types (int.MaxValue and string.Empty) causes completion to trigger automatically, - ' even when showCompletionInArgumentLists is disabled. - Await state.AssertSignatureHelpSession() - - state.SendTypeChars("A") - Await state.AssertSelectedCompletionItem(displayText:="Alice:", isHardSelected:=True) - state.SendTypeChars(":") - Assert.Contains("new C(Alice:", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) - End Using - End Function - diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs index 188ca88c4c20..b4a3f437b8b4 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProvider.cs @@ -35,6 +35,8 @@ internal partial class EnumAndCompletionListTagCompletionProvider : LSPCompletio .WithMatchPriority(MatchPriority.Preselect) .WithSelectionBehavior(CompletionItemSelectionBehavior.HardSelection); + private static readonly ImmutableHashSet s_triggerCharacters = ImmutableHashSet.Create(' ', '[', '(', '~'); + [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public EnumAndCompletionListTagCompletionProvider() @@ -58,7 +60,7 @@ public override bool IsInsertionTrigger(SourceText text, int characterPosition, (options.GetOption(CompletionOptions.TriggerOnTypingLetters2, LanguageNames.CSharp) && CompletionUtilities.IsStartingNewWord(text, characterPosition)); } - public override ImmutableHashSet TriggerCharacters { get; } = ImmutableHashSet.Create(' ', '[', '(', '~'); + public override ImmutableHashSet TriggerCharacters => s_triggerCharacters; public override async Task ProvideCompletionsAsync(CompletionContext context) { @@ -128,6 +130,13 @@ private static async Task HandleSingleTypeAsync(CompletionContext context, Seman if (enumType == null) { + if (context.Trigger.Kind == CompletionTriggerKind.Insertion && s_triggerCharacters.Contains(context.Trigger.Character)) + { + // This completion provider understands static members of matching types, but doesn't + // proactively trigger completion for them to avoid interfering with common typing patterns. + return; + } + // If this isn't an enum or marked with completionlist, also check if it contains static members of // a matching type. These 'enum-like' types have similar characteristics to enum completion, but do // not show the containing type as a separate item in completion.