From c492a3d010aa124b34aaae87a20bcd2b8f90cf22 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Mon, 20 Apr 2020 13:38:42 -0700 Subject: [PATCH 1/2] Fix up asserting in TypeInferrerTests.vb It turns out the VB TypeInferrer tests never actually asserted the resulting type is the type that we expected. --- .../VisualBasicTest/TypeInferrer/TypeInferrerTests.vb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb b/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb index 7bbe08ccafa33..49b8c37998988 100644 --- a/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb @@ -33,6 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.TypeInferrer End If Dim typeSyntax = inferredType.GenerateTypeSyntax().NormalizeWhitespace() + Assert.Equal(expectedType, typeSyntax.ToString()) End Function Private Async Function TestInClassAsync(text As String, expectedType As String, mode As TestMode) As Tasks.Task @@ -701,17 +702,17 @@ End Class End Function - + Public Async Function TestValueInNestedTuple1(mode As TestMode) As Task Await TestInMethodAsync( -"dim x as (integer, (string, boolean)) = ([|Goo()|], ("""", true));", "global::System.Int32", mode) +"dim x as (integer, (string, boolean)) = ([|Goo()|], ("""", true));", "System.Int32", mode) End Function - + Public Async Function TestValueInNestedTuple2(mode As TestMode) As Task Await TestInMethodAsync( -"dim x as (integer, (string, boolean)) = (1, ("""", [|Goo()|]))", "global::System.Boolean", mode) +"dim x as (integer, (string, boolean)) = (1, ("""", [|Goo()|]))", "System.Boolean", mode) End Function @@ -818,7 +819,7 @@ Class C Return Await [||] End Function End Class" - Await TestAsync(text, "Task.FromResult(False)", TestMode.Position) + Await TestAsync(text, "Global.System.Threading.Tasks.Task(Of System.Boolean)", TestMode.Position) End Function End Class End Namespace From f54cd3a74d9fe2185c72f5f0f3b47769d5228d00 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Mon, 20 Apr 2020 14:14:45 -0700 Subject: [PATCH 2/2] Update the TypeInferrers to infer a better type with Enum.HasFlags This allows enum completion to correctly offer the enum options immediately which is likely what the user is trying to do. --- ...CompletionListTagCompletionProviderTests.cs | 17 +++++++++++++++++ .../TypeInferrer/TypeInferrerTests.cs | 18 ++++++++++++++++++ .../EnumCompletionProviderTests.vb | 15 +++++++++++++++ .../TypeInferrer/TypeInferrerTests.vb | 15 +++++++++++++++ .../CSharpTypeInferenceService.TypeInferrer.cs | 17 ++++++++++++++++- ...ypeInferenceService.AbstractTypeInferrer.cs | 6 ++++++ ...alBasicTypeInferenceService.TypeInferrer.vb | 13 +++++++++++++ 7 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs index c26b16cbc635a..797a4b290678b 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/EnumAndCompletionListTagCompletionProviderTests.cs @@ -818,5 +818,22 @@ internal enum ProjectTreeWriterOptions }"; await VerifyItemExistsAsync(markup, "ProjectTreeWriterOptions"); } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestInEnumHasFlag() + { + var markup = +@"using System.IO; + +class C +{ + void M() + { + FileInfo f; + f.Attributes.HasFlag($$ + } +}"; + await VerifyItemExistsAsync(markup, "FileAttributes"); + } } } diff --git a/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs b/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs index 0621fa9a1e46c..ee0ab1a3e583b 100644 --- a/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs +++ b/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs @@ -3077,5 +3077,23 @@ public async Task TestValueInNestedTuple3() await TestInMethodAsync( @"(int, string) x = (1, [||]);", "global::System.String", TestMode.Position); } + + [Theory, CombinatorialData, Trait(Traits.Feature, Traits.Features.TypeInferenceService)] + public async Task TestInferringInEnumHasFlags(TestMode mode) + { + var text = +@"using System.IO; + +class Program +{ + static void Main(string[] args) + { + FileInfo f; + f.Attributes.HasFlag([|flag|]); + } +}"; + + await TestAsync(text, "global::System.IO.FileAttributes", mode); + } } } diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/EnumCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/EnumCompletionProviderTests.vb index 29df51b677985..acb68c0861d7f 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/EnumCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/EnumCompletionProviderTests.vb @@ -538,5 +538,20 @@ End Class ]]>.Value Await VerifyItemExistsAsync(markup, "DayOfWeek.Monday") End Function + + + Public Async Function TestInEnumHasFlag() As Task + Dim markup = .Value + Await VerifyItemExistsAsync(markup, "FileAttributes.Hidden") + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb b/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb index 49b8c37998988..b290cc948e0da 100644 --- a/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/TypeInferrer/TypeInferrerTests.vb @@ -821,5 +821,20 @@ Class C End Class" Await TestAsync(text, "Global.System.Threading.Tasks.Task(Of System.Boolean)", TestMode.Position) End Function + + + Public Async Function TestInferringInEnumHasFlags(mode As TestMode) As Task + Dim text = +"Imports System.IO + +Module Program + Sub Main(args As String()) + Dim f As FileInfo + f.Attributes.HasFlag([|flag|]) + End Sub +End Module" + + Await TestAsync(text, "Global.System.IO.FileAttributes", mode) + End Function End Class End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs index 851cfba60f2f2..070fb55670ea9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs @@ -467,7 +467,22 @@ private IEnumerable InferTypeInInvocationExpression( SemanticModel.GetMemberGroup(invocation.Expression, CancellationToken) .OfType(); - methods = methods.Concat(memberGroupMethods).Distinct(); + methods = methods.Concat(memberGroupMethods).Distinct().ToList(); + } + + // Special case: if this is an argument in Enum.HasFlag, infer the Enum type that we're invoking into, + // as otherwise we infer "Enum" which isn't useful + if (methods.Any(IsEnumHasFlag)) + { + if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) + { + var typeInfo = SemanticModel.GetTypeInfo(memberAccess.Expression, CancellationToken); + + if (typeInfo.Type != null && typeInfo.Type.IsEnumType()) + { + return CreateResult(typeInfo.Type); + } + } } return InferTypeInArgument(index, methods, argumentOpt, invocation); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs index ac40e9cd39ac1..5e1f18fb4d630 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/TypeInferenceService/AbstractTypeInferenceService.AbstractTypeInferrer.cs @@ -117,6 +117,12 @@ protected static IEnumerable GetCollectionElementType(INamedT return SpecializedCollections.EmptyEnumerable(); } + + protected static bool IsEnumHasFlag(ISymbol symbol) + { + return symbol.Name == nameof(Enum.HasFlag) && + symbol.ContainingType?.SpecialType == SpecialType.System_Enum; + } } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb index b7e5a25b6f998..baabafed90c3b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicTypeInferenceService.TypeInferrer.vb @@ -235,6 +235,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' called with argument at this position. Note: if they're calling an extension ' method then it will need one more argument in order for us to call it. Dim symbols = info.GetBestOrAllSymbols() + + ' Special case If this Is an Then argument In Enum.HasFlag, infer the Enum type that we're invoking into, + ' as otherwise we infer "Enum" which isn't useful + If symbols.Any(AddressOf IsEnumHasFlag) Then + Dim memberAccess = TryCast(invocation.Expression, MemberAccessExpressionSyntax) + If memberAccess IsNot Nothing Then + Dim typeInfo = SemanticModel.GetTypeInfo(memberAccess.Expression, CancellationToken) + If typeInfo.Type IsNot Nothing AndAlso typeInfo.Type.IsEnumType() Then + Return CreateResult(typeInfo.Type) + End If + End If + End If + If symbols.Any() Then Return InferTypeInArgument(argumentOpt, index, symbols) Else