提交 a4fb66a3 编写于 作者: J Jason Malinowski

Move the Quick Info handling for await back into Quick Info

The Quick Info service mostly defers to the symbol description service
to do most of it's work; the symbol description service takes a symbol
and a SemanticModel + position to build up the description of the
symbol, using that position for minimally qualifying types in some
cases. This position was also being used by the symbol description
service to see if the original invocation point was an await, and if so
it switched to special behavior to give a different message entirely;
there was some logic in Quick Info itself to also accomodate that.

This moves that special handling back up to Quick Info, and also does
a bit of a refactoring to include all of the decisions for what Quick
Info will show into a struct. This will be used later to help the LSIF
tool because it means if the two structs are equal than we know the
two Quick Info contents would the same and we can reuse it.
上级 b08173bc
......@@ -1345,7 +1345,7 @@ async Task UseAsync()
result = await lambda();
}
}";
await TestAsync(markup, MainDescription($"({CSharpFeaturesResources.awaitable}) {string.Format(FeaturesResources.Awaited_task_returns_0, "class System.Threading.Tasks.Task<TResult>")}"),
await TestAsync(markup, MainDescription(string.Format(FeaturesResources.Awaited_task_returns_0, $"({CSharpFeaturesResources.awaitable}) class System.Threading.Tasks.Task<TResult>")),
TypeParameterMap($"\r\nTResult {FeaturesResources.is_} int"));
}
......@@ -5230,7 +5230,7 @@ async Task<int[]> M()
awa$$it M();
}
}";
await TestAsync(markup, MainDescription("int[]"));
await TestAsync(markup, MainDescription(string.Format(FeaturesResources.Awaited_task_returns_0, "int[]")));
}
[WorkItem(1114300, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1114300")]
......@@ -5247,7 +5247,7 @@ async Task<dynamic> M()
awa$$it M();
}
}";
await TestAsync(markup, MainDescription("dynamic"));
await TestAsync(markup, MainDescription(string.Format(FeaturesResources.Awaited_task_returns_0, "dynamic")));
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
......
......@@ -1897,7 +1897,7 @@ End Class
</Project>
</Workspace>.ToString()
Dim description = <File>&lt;<%= VBFeaturesResources.Awaitable %>&gt; <%= String.Format(FeaturesResources.Awaited_task_returns_0, "Class System.Threading.Tasks.Task(Of TResult)") %></File>.ConvertTestSourceTag()
Dim description = <File><%= String.Format(FeaturesResources.Awaited_task_returns_0, $"<{VBFeaturesResources.Awaitable}> Class System.Threading.Tasks.Task(Of TResult)") %></File>.ConvertTestSourceTag()
Await TestFromXmlAsync(markup, MainDescription(description), TypeParameterMap(vbCrLf & $"TResult {FeaturesResources.is_} Integer"))
End Function
......
......@@ -283,7 +283,7 @@ private async Task AddDescriptionPartAsync(ISymbol symbol)
}
else
{
await AddDescriptionForNamedTypeAsync(namedType).ConfigureAwait(false);
AddDescriptionForNamedType(namedType);
}
}
else if (symbol is INamespaceSymbol namespaceSymbol)
......@@ -380,37 +380,14 @@ private void AddDescriptionForDynamicType()
PlainText(FeaturesResources.Represents_an_object_whose_operations_will_be_resolved_at_runtime));
}
private async Task AddDescriptionForNamedTypeAsync(INamedTypeSymbol symbol)
private void AddDescriptionForNamedType(INamedTypeSymbol symbol)
{
if (symbol.IsAwaitableNonDynamic(_semanticModel, _position))
{
AddAwaitablePrefix();
}
var token = await _semanticModel.SyntaxTree.GetTouchingTokenAsync(_position, CancellationToken).ConfigureAwait(false);
if (token != default)
{
var syntaxFactsService = Workspace.Services.GetLanguageServices(token.Language).GetService<ISyntaxFactsService>();
if (syntaxFactsService.IsAwaitKeyword(token))
{
if (symbol.SpecialType == SpecialType.System_Void)
{
AddToGroup(SymbolDescriptionGroups.MainDescription,
PlainText(FeaturesResources.Awaited_task_returns_no_value));
return;
}
AddAwaitSymbolDescription(symbol);
}
else
{
AddSymbolDescription(symbol);
}
}
else
{
AddSymbolDescription(symbol);
}
AddSymbolDescription(symbol);
if (!symbol.IsUnboundGenericType && !TypeArgumentsAndParametersAreSame(symbol))
{
......@@ -421,18 +398,6 @@ private async Task AddDescriptionForNamedTypeAsync(INamedTypeSymbol symbol)
}
}
private void AddAwaitSymbolDescription(INamedTypeSymbol symbol)
{
var defaultSymbol = "{0}";
var symbolIndex = FeaturesResources.Awaited_task_returns_0.IndexOf(defaultSymbol);
AddToGroup(SymbolDescriptionGroups.MainDescription,
PlainText(FeaturesResources.Awaited_task_returns_0.Substring(0, symbolIndex)));
AddSymbolDescription(symbol);
AddToGroup(SymbolDescriptionGroups.MainDescription,
PlainText(FeaturesResources.Awaited_task_returns_0.Substring(symbolIndex + defaultSymbol.Length)));
}
private void AddSymbolDescription(INamedTypeSymbol symbol)
{
if (symbol.TypeKind == TypeKind.Delegate)
......
// 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.
#nullable enable
using System.Collections.Immutable;
namespace Microsoft.CodeAnalysis.QuickInfo
{
internal abstract partial class CommonSemanticQuickInfoProvider
{
public struct TokenInformation
{
public readonly ImmutableArray<ISymbol> Symbols;
/// <summary>
/// True if this quick info came from hovering over an 'await' keyword, which we show the return
/// type of with special text.
/// </summary>
public readonly bool ShowAwaitReturn;
public TokenInformation(ImmutableArray<ISymbol> symbols, bool showAwaitReturn = false)
{
Symbols = symbols;
ShowAwaitReturn = showAwaitReturn;
}
}
}
}
......@@ -26,19 +26,19 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
SyntaxToken token,
CancellationToken cancellationToken)
{
var (model, symbols, supportedPlatforms) = await ComputeQuickInfoDataAsync(document, token, cancellationToken).ConfigureAwait(false);
var (model, tokenInformation, supportedPlatforms) = await ComputeQuickInfoDataAsync(document, token, cancellationToken).ConfigureAwait(false);
if (symbols.IsDefaultOrEmpty)
if (tokenInformation.Symbols.IsDefaultOrEmpty)
{
return null;
}
return await CreateContentAsync(document.Project.Solution.Workspace,
token, model, symbols, supportedPlatforms,
token, model, tokenInformation, supportedPlatforms,
cancellationToken).ConfigureAwait(false);
}
private async Task<(SemanticModel model, ImmutableArray<ISymbol> symbols, SupportedPlatformData? supportedPlatforms)> ComputeQuickInfoDataAsync(
private async Task<(SemanticModel model, TokenInformation tokenInformation, SupportedPlatformData? supportedPlatforms)> ComputeQuickInfoDataAsync(
Document document,
SyntaxToken token,
CancellationToken cancellationToken)
......@@ -49,12 +49,12 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
return await ComputeFromLinkedDocumentsAsync(document, linkedDocumentIds, token, cancellationToken).ConfigureAwait(false);
}
var (model, symbols) = await BindTokenAsync(document, token, cancellationToken).ConfigureAwait(false);
var (model, tokenInformation) = await BindTokenAsync(document, token, cancellationToken).ConfigureAwait(false);
return (model, symbols, supportedPlatforms: null);
return (model, tokenInformation, supportedPlatforms: null);
}
private async Task<(SemanticModel model, ImmutableArray<ISymbol> symbols, SupportedPlatformData supportedPlatforms)> ComputeFromLinkedDocumentsAsync(
private async Task<(SemanticModel model, TokenInformation, SupportedPlatformData supportedPlatforms)> ComputeFromLinkedDocumentsAsync(
Document document,
ImmutableArray<DocumentId> linkedDocumentIds,
SyntaxToken token,
......@@ -70,14 +70,14 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
// Instead, we need to find the head in which we get the best binding,
// which in this case is the one with no errors.
var (model, symbols) = await BindTokenAsync(document, token, cancellationToken).ConfigureAwait(false);
var (model, tokenInformation) = await BindTokenAsync(document, token, cancellationToken).ConfigureAwait(false);
var candidateProjects = new List<ProjectId>() { document.Project.Id };
var invalidProjects = new List<ProjectId>();
var candidateResults = new List<(DocumentId docId, SemanticModel model, ImmutableArray<ISymbol> symbols)>
var candidateResults = new List<(DocumentId docId, SemanticModel model, TokenInformation tokenInformation)>
{
(document.Id, model, symbols)
(document.Id, model, tokenInformation)
};
foreach (var linkedDocumentId in linkedDocumentIds)
......@@ -96,10 +96,10 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
// Take the first result with no errors.
// If every file binds with errors, take the first candidate, which is from the current file.
var bestBinding = candidateResults.FirstOrNull(c => HasNoErrors(c.symbols))
var bestBinding = candidateResults.FirstOrNull(c => HasNoErrors(c.tokenInformation.Symbols))
?? candidateResults.First();
if (bestBinding.symbols.IsDefaultOrEmpty)
if (bestBinding.tokenInformation.Symbols.IsDefaultOrEmpty)
{
return default;
}
......@@ -109,7 +109,7 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
foreach (var candidate in candidateResults)
{
// Does the candidate have anything remotely equivalent?
if (!candidate.symbols.Intersect(bestBinding.symbols, LinkedFilesSymbolEquivalenceComparer.Instance).Any())
if (!candidate.tokenInformation.Symbols.Intersect(bestBinding.tokenInformation.Symbols, LinkedFilesSymbolEquivalenceComparer.Instance).Any())
{
invalidProjects.Add(candidate.docId.ProjectId);
}
......@@ -117,7 +117,7 @@ internal abstract partial class CommonSemanticQuickInfoProvider : CommonQuickInf
var supportedPlatforms = new SupportedPlatformData(invalidProjects, candidateProjects, document.Project.Solution.Workspace);
return (bestBinding.model, bestBinding.symbols, supportedPlatforms);
return (bestBinding.model, bestBinding.tokenInformation, supportedPlatforms);
}
private static bool HasNoErrors(ImmutableArray<ISymbol> symbols)
......@@ -152,17 +152,17 @@ private static bool HasNoErrors(ImmutableArray<ISymbol> symbols)
Workspace workspace,
SyntaxToken token,
SemanticModel semanticModel,
IEnumerable<ISymbol> symbols,
TokenInformation tokenInformation,
SupportedPlatformData? supportedPlatforms,
CancellationToken cancellationToken)
{
var descriptionService = workspace.Services.GetLanguageServices(token.Language).GetRequiredService<ISymbolDisplayService>();
var formatter = workspace.Services.GetLanguageServices(semanticModel.Language).GetRequiredService<IDocumentationCommentFormattingService>();
var syntaxFactsService = workspace.Services.GetLanguageServices(semanticModel.Language).GetRequiredService<ISyntaxFactsService>();
var showWarningGlyph = supportedPlatforms != null && supportedPlatforms.HasValidAndInvalidProjects();
var showSymbolGlyph = true;
var groups = await descriptionService.ToDescriptionGroupsAsync(workspace, semanticModel, token.SpanStart, symbols.AsImmutable(), cancellationToken).ConfigureAwait(false);
var groups = await descriptionService.ToDescriptionGroupsAsync(workspace, semanticModel, token.SpanStart, tokenInformation.Symbols, cancellationToken).ConfigureAwait(false);
bool TryGetGroupText(SymbolDescriptionGroups group, out ImmutableArray<TaggedText> taggedParts)
=> groups.TryGetValue(group, out taggedParts) && !taggedParts.IsDefaultOrEmpty;
......@@ -172,12 +172,42 @@ bool TryGetGroupText(SymbolDescriptionGroups group, out ImmutableArray<TaggedTex
void AddSection(string kind, ImmutableArray<TaggedText> taggedParts)
=> sections.Add(QuickInfoSection.Create(kind, taggedParts));
if (TryGetGroupText(SymbolDescriptionGroups.MainDescription, out var mainDescriptionTaggedParts))
if (tokenInformation.ShowAwaitReturn)
{
AddSection(QuickInfoSectionKinds.Description, mainDescriptionTaggedParts);
// We show a special message if the Task being awaited has no return
if ((tokenInformation.Symbols.First() as INamedTypeSymbol)?.SpecialType == SpecialType.System_Void)
{
var builder = ImmutableArray.CreateBuilder<TaggedText>();
builder.AddText(FeaturesResources.Awaited_task_returns_no_value);
AddSection(QuickInfoSectionKinds.Description, builder.ToImmutable());
return QuickInfoItem.Create(token.Span, sections: sections.ToImmutable());
}
else
{
if (TryGetGroupText(SymbolDescriptionGroups.MainDescription, out var mainDescriptionTaggedParts))
{
// We'll take the existing message and wrap it with a message saying this was returned from the task.
var defaultSymbol = "{0}";
var symbolIndex = FeaturesResources.Awaited_task_returns_0.IndexOf(defaultSymbol);
var builder = ImmutableArray.CreateBuilder<TaggedText>();
builder.AddText(FeaturesResources.Awaited_task_returns_0.Substring(0, symbolIndex));
builder.AddRange(mainDescriptionTaggedParts);
builder.AddText(FeaturesResources.Awaited_task_returns_0.Substring(symbolIndex + defaultSymbol.Length));
AddSection(QuickInfoSectionKinds.Description, builder.ToImmutable());
}
}
}
else
{
if (TryGetGroupText(SymbolDescriptionGroups.MainDescription, out var mainDescriptionTaggedParts))
{
AddSection(QuickInfoSectionKinds.Description, mainDescriptionTaggedParts);
}
}
var documentedSymbol = symbols.FirstOrDefault();
var documentedSymbol = tokenInformation.Symbols.First();
// if generating quick info for an attribute, bind to the class instead of the constructor
if (syntaxFactsService.IsAttributeName(token.Parent) &&
......@@ -187,12 +217,6 @@ void AddSection(string kind, ImmutableArray<TaggedText> taggedParts)
}
var documentationContent = GetDocumentationContent(documentedSymbol, groups, semanticModel, token, formatter, cancellationToken);
if (syntaxFactsService.IsAwaitKeyword(token) &&
(symbols.First() as INamedTypeSymbol)?.SpecialType == SpecialType.System_Void)
{
documentationContent = default;
showSymbolGlyph = false;
}
if (!documentationContent.IsDefaultOrEmpty)
{
......@@ -286,11 +310,7 @@ void AddSection(string kind, ImmutableArray<TaggedText> taggedParts)
AddSection(QuickInfoSectionKinds.Captures, capturesText);
}
var tags = ImmutableArray<string>.Empty;
if (showSymbolGlyph)
{
tags = tags.AddRange(GlyphTags.GetTags(symbols.First().GetGlyph()));
}
var tags = ImmutableArray.CreateRange(GlyphTags.GetTags(tokenInformation.Symbols.First().GetGlyph()));
if (showWarningGlyph)
{
......@@ -408,10 +428,11 @@ void AddSection(string kind, ImmutableArray<TaggedText> taggedParts)
protected virtual ImmutableArray<TaggedText> TryGetNullabilityAnalysis(Workspace workspace, SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) => default;
private async Task<(SemanticModel semanticModel, ImmutableArray<ISymbol> symbols)> BindTokenAsync(
private async Task<(SemanticModel semanticModel, TokenInformation tokenInformation)> BindTokenAsync(
Document document, SyntaxToken token, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var isAwait = syntaxFacts.IsAwaitKeyword(token);
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var enclosingType = semanticModel.GetEnclosingNamedType(token.SpanStart, cancellationToken);
......@@ -431,7 +452,7 @@ void AddSection(string kind, ImmutableArray<TaggedText> taggedParts)
if (symbols.Any())
{
var discardSymbols = (symbols.First() as ITypeParameterSymbol)?.TypeParameterKind == TypeParameterKind.Cref;
return (semanticModel, discardSymbols ? ImmutableArray<ISymbol>.Empty : symbols);
return (semanticModel, new TokenInformation(discardSymbols ? ImmutableArray<ISymbol>.Empty : symbols, isAwait));
}
// Couldn't bind the token to specific symbols. If it's an operator, see if we can at
......@@ -441,11 +462,11 @@ void AddSection(string kind, ImmutableArray<TaggedText> taggedParts)
var typeInfo = semanticModel.GetTypeInfo(token.Parent!, cancellationToken);
if (IsOk(typeInfo.Type))
{
return (semanticModel, ImmutableArray.Create<ISymbol>(typeInfo.Type));
return (semanticModel, new TokenInformation(ImmutableArray.Create<ISymbol>(typeInfo.Type)));
}
}
return (semanticModel, ImmutableArray<ISymbol>.Empty);
return (semanticModel, new TokenInformation(ImmutableArray<ISymbol>.Empty));
}
private ImmutableArray<ISymbol> GetSymbolsFromToken(SyntaxToken token, Workspace workspace, SemanticModel semanticModel, CancellationToken cancellationToken)
......
......@@ -121,7 +121,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo
Return False
End Function
Private Overloads Async Function BuildContentAsync(
Private Overloads Shared Async Function BuildContentAsync(
document As Document,
token As SyntaxToken,
declarators As SeparatedSyntaxList(Of VariableDeclaratorSyntax),
......@@ -134,7 +134,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo
Dim semantics = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Dim types = declarators.SelectMany(Function(d) d.Names).Select(
Function(n)
Function(n) As ISymbol
Dim symbol = semantics.GetDeclaredSymbol(n, cancellationToken)
If symbol Is Nothing Then
Return Nothing
......@@ -147,17 +147,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.QuickInfo
Else
Return Nothing
End If
End Function).WhereNotNull().Distinct().ToList()
End Function).WhereNotNull().Distinct().ToImmutableArray()
If types.Count = 0 Then
If types.Length = 0 Then
Return Nothing
End If
If types.Count > 1 Then
If types.Length > 1 Then
Return QuickInfoItem.Create(token.Span, sections:=ImmutableArray.Create(QuickInfoSection.Create(QuickInfoSectionKinds.Description, ImmutableArray.Create(New TaggedText(TextTags.Text, VBFeaturesResources.Multiple_Types)))))
End If
Return Await CreateContentAsync(document.Project.Solution.Workspace, token, semantics, types, supportedPlatforms:=Nothing, cancellationToken:=cancellationToken).ConfigureAwait(False)
Return Await CreateContentAsync(document.Project.Solution.Workspace, token, semantics, New TokenInformation(types), supportedPlatforms:=Nothing, cancellationToken:=cancellationToken).ConfigureAwait(False)
End Function
Private Shared Async Function BuildContentForIntrinsicOperatorAsync(document As Document,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册