Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show a specialized display for anonymous delegates in C# #58908

Merged
merged 16 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7914,6 +7914,7 @@ void M()
TResult {FeaturesResources.is_} string"));
}

[WorkItem(58871, "https://github.com/dotnet/roslyn/issues/58871")]
[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task TestAnonymousSynthesizedLambdaType()
{
Expand All @@ -7925,7 +7926,49 @@ void M()
$$var v = (ref int i) => i.ToString();
}
}",
MainDescription("delegate string <anonymous delegate>(ref int)"));
MainDescription("delegate string <anonymous delegate>(ref int)"),
AnonymousTypes(""));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the top level we still show it like this. It's the inner references to an anonymous type that go in the Types: section.

}

[WorkItem(58871, "https://github.com/dotnet/roslyn/issues/58871")]
[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task TestAnonymousSynthesizedLambdaType2()
{
await TestAsync(
@"class C
{
void M()
{
var $$v = (ref int i) => i.ToString();
}
}",
MainDescription("(local variable) 'a v"),
AnonymousTypes(
$@"
{FeaturesResources.Types_colon}
'a {FeaturesResources.is_} string delegate(ref int)"));
}

[WorkItem(58871, "https://github.com/dotnet/roslyn/issues/58871")]
[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task TestAnonymousSynthesizedLambdaType3()
{
await TestAsync(
@"class C
{
void M()
{
var v = (ref int i) => i.ToString();
$$Goo(v);
}

T Goo<T>(T t) => default;
}",
MainDescription("'a C.Goo<'a>('a t)"),
AnonymousTypes(
$@"
{FeaturesResources.Types_colon}
'a {FeaturesResources.is_} string delegate(ref int)"));
}

[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,8 @@ Module Program
End Sub
End Module
]]></Text>.NormalizedValue,
MainDescription($"({FeaturesResources.local_variable}) a As <Sub()>"))
MainDescription($"({FeaturesResources.local_variable}) a As 'a"),
AnonymousTypes(vbCrLf & FeaturesResources.Types_colon & vbCrLf & $" 'a {FeaturesResources.is_} <Sub()>"))
End Function

<WorkItem(543624, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543624")>
Expand All @@ -966,7 +967,8 @@ Module Program
End Sub
End Module
]]></Text>.NormalizedValue,
MainDescription($"({FeaturesResources.local_variable}) a As <Function() As Integer>"))
MainDescription($"({FeaturesResources.local_variable}) a As 'a"),
AnonymousTypes(vbCrLf & FeaturesResources.Types_colon & vbCrLf & $" 'a {FeaturesResources.is_} <Function() As Integer>"))
End Function

<WorkItem(543624, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543624")>
Expand All @@ -981,9 +983,10 @@ Module Program
End Sub
End Module
]]></Text>.NormalizedValue,
MainDescription($"({FeaturesResources.local_variable}) a As <Function() As 'a>"),
MainDescription($"({FeaturesResources.local_variable}) a As 'a"),
AnonymousTypes(vbCrLf & FeaturesResources.Types_colon & vbCrLf &
$" 'a {FeaturesResources.is_} New With {{ .Goo As String }}"))
$" 'a {FeaturesResources.is_} <Function() As 'b>" & vbCrLf &
$" 'b {FeaturesResources.is_} New With {{ .Goo As String }}"))
End Function

<WorkItem(543624, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543624")>
Expand All @@ -999,9 +1002,11 @@ Module Program
End Sub
End Module
]]></Text>.NormalizedValue,
MainDescription($"({FeaturesResources.local_variable}) a As <Function(i As Integer) As 'a>"),
MainDescription($"({FeaturesResources.local_variable}) a As 'a"),
AnonymousTypes(vbCrLf & FeaturesResources.Types_colon & vbCrLf &
$" 'a {FeaturesResources.is_} New With {{ .Sq As Integer, .M As <Function(j As Integer) As Integer> }}"))
$" 'a {FeaturesResources.is_} <Function(i As Integer) As 'b>" & vbCrLf &
$" 'b {FeaturesResources.is_} New With {{ .Sq As Integer, .M As 'c }}" & vbCrLf &
$" 'c {FeaturesResources.is_} <Function(j As Integer) As Integer>"))
End Function

<WorkItem(543389, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543389")>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,26 @@
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.CSharp.LanguageServices
{
[ExportLanguageService(typeof(IStructuralTypeDisplayService), LanguageNames.CSharp), Shared]
internal class CSharpStructuralTypeDisplayService : AbstractStructuralTypeDisplayService
{
private static readonly SymbolDisplayFormat s_minimalWithoutContainingType =
s_minimalWithoutExpandedTuples.WithMemberOptions(s_minimalWithoutExpandedTuples.MemberOptions & ~SymbolDisplayMemberOptions.IncludeContainingType);

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpStructuralTypeDisplayService()
{
}

public override ImmutableArray<SymbolDisplayPart> GetAnonymousTypeParts(
INamedTypeSymbol anonymousType, SemanticModel semanticModel, int position)
protected override ImmutableArray<SymbolDisplayPart> GetNormalAnonymousTypeParts(
INamedTypeSymbol anonymousType,
SemanticModel semanticModel,
int position)
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
{
using var _ = ArrayBuilder<SymbolDisplayPart>.GetInstance(out var members);

Expand Down Expand Up @@ -56,5 +62,27 @@ public override ImmutableArray<SymbolDisplayPart> GetAnonymousTypeParts(

return members.ToImmutable();
}

protected override ImmutableArray<SymbolDisplayPart> GetDelegateAnonymousTypeParts(
INamedTypeSymbol anonymousType,
SemanticModel semanticModel,
int position)
{
using var _ = ArrayBuilder<SymbolDisplayPart>.GetInstance(out var parts);

parts.AddRange(anonymousType.DelegateInvokeMethod.ToMinimalDisplayParts(semanticModel, position, s_minimalWithoutContainingType));

// change the display from `bool Invoke(int x, string y)` to `bool delegate(int x, string y)`. `delegate`
// helps make it clear what sort of signature we're showing, and the lack of the name is appropriate as this
// is an anonymous delegate.
var namePart = parts.FirstOrNull(p => p.Kind == SymbolDisplayPartKind.MethodName && p.ToString() == anonymousType.DelegateInvokeMethod.Name);
if (namePart != null)
{
var index = parts.IndexOf(namePart.Value);
parts[index] = new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, null, "delegate");
}

return parts.ToImmutable();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,6 @@ protected override void AddCaptures(ISymbol symbol)
}
}

protected override void InlineAllDelegateAnonymousTypes(SemanticModel semanticModel, int position, IStructuralTypeDisplayService structuralTypeDisplayService, Dictionary<SymbolDescriptionGroups, IList<SymbolDisplayPart>> groupMap)
{
// In C#, anonymous delegates are typically represented with System.Action<> or System.Func<>,
// and we prefer to display those types rather than a structural delegate type.
}

protected override SymbolDisplayFormat MinimallyQualifiedFormat => s_minimallyQualifiedFormat;

protected override SymbolDisplayFormat MinimallyQualifiedFormatWithConstants => s_minimallyQualifiedFormatWithConstants;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public override void VisitNamedType(INamedTypeSymbol symbol)
}
else if (symbol.IsAnonymousDelegateType())
{
_namedTypes.Add(symbol, (order: _namedTypes.Count, count: 1));
symbol.DelegateInvokeMethod?.Accept(this);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.LanguageServices
Expand All @@ -15,8 +16,13 @@ internal abstract partial class AbstractStructuralTypeDisplayService : IStructur
protected static readonly SymbolDisplayFormat s_minimalWithoutExpandedTuples = SymbolDisplayFormat.MinimallyQualifiedFormat.AddMiscellaneousOptions(
SymbolDisplayMiscellaneousOptions.CollapseTupleTypes);

public abstract ImmutableArray<SymbolDisplayPart> GetAnonymousTypeParts(
INamedTypeSymbol anonymousType, SemanticModel semanticModel, int position);
protected abstract ImmutableArray<SymbolDisplayPart> GetNormalAnonymousTypeParts(INamedTypeSymbol anonymousType, SemanticModel semanticModel, int position);
protected abstract ImmutableArray<SymbolDisplayPart> GetDelegateAnonymousTypeParts(INamedTypeSymbol anonymousType, SemanticModel semanticModel, int position);

public ImmutableArray<SymbolDisplayPart> GetAnonymousTypeParts(INamedTypeSymbol anonymousType, SemanticModel semanticModel, int position)
=> anonymousType.IsAnonymousDelegateType()
? GetDelegateAnonymousTypeParts(anonymousType, semanticModel, position)
: GetNormalAnonymousTypeParts(anonymousType, semanticModel, position);

public StructuralTypeDisplayInfo GetTypeDisplayInfo(
ISymbol orderSymbol,
Expand Down Expand Up @@ -47,9 +53,12 @@ public StructuralTypeDisplayInfo GetTypeDisplayInfo(

var structuralType = transitiveStructuralTypeReferences[i];
typeParts.AddRange(Space(count: 4));
typeParts.Add(Part(
structuralType.IsValueType ? SymbolDisplayPartKind.StructName : SymbolDisplayPartKind.ClassName,
structuralType, structuralType.Name));

var kind =
structuralType.IsValueType ? SymbolDisplayPartKind.StructName :
structuralType.IsDelegateType() ? SymbolDisplayPartKind.DelegateName : SymbolDisplayPartKind.ClassName;

typeParts.Add(Part(kind, structuralType, structuralType.Name));
typeParts.AddRange(Space());
typeParts.Add(PlainText(FeaturesResources.is_));
typeParts.AddRange(Space());
Expand All @@ -64,9 +73,6 @@ public StructuralTypeDisplayInfo GetTypeDisplayInfo(
}
}

// Now, inline any delegate anonymous types we've got.
typeParts = this.InlineDelegateAnonymousTypes(typeParts, semanticModel, position);

// Finally, assign a name to all the anonymous types.
var structuralTypeToName = GenerateStructuralTypeNames(transitiveStructuralTypeReferences);
typeParts = StructuralTypeDisplayInfo.ReplaceStructuralTypes(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
Expand All @@ -17,24 +14,20 @@ protected abstract partial class AbstractSymbolDescriptionBuilder
{
private void FixAllStructuralTypes(ISymbol firstSymbol)
{
// First, inline all the delegate anonymous types. This is how VB prefers to display
// things.
InlineAllDelegateAnonymousTypes(_semanticModel, _position, _structuralTypeDisplayService, _groupMap);

// Now, replace all normal anonymous types and tuples with 'a, 'b, etc. and create a
// Structural Types: section to display their info.
FixStructuralTypes(firstSymbol);
}

protected abstract void InlineAllDelegateAnonymousTypes(SemanticModel semanticModel, int position, IStructuralTypeDisplayService structuralTypeDisplayService, Dictionary<SymbolDescriptionGroups, IList<SymbolDisplayPart>> groupMap);

private void FixStructuralTypes(ISymbol firstSymbol)
{
var directStructuralTypes =
from parts in _groupMap.Values
from part in parts
where part.Symbol.IsNormalAnonymousType() || part.Symbol.IsTupleType()
select (INamedTypeSymbol)part.Symbol;
where part.Symbol.IsAnonymousType() || part.Symbol.IsTupleType()
select (INamedTypeSymbol)part.Symbol!;

// If the first symbol is an anonymous delegate, just show it's full sig in-line in the main
// description. Otherwise, replace it with 'a, 'b etc. and show its sig in the 'Types:' section.

if (firstSymbol.IsAnonymousDelegateType())
directStructuralTypes = directStructuralTypes.Except(new[] { (INamedTypeSymbol)firstSymbol });

var info = _structuralTypeDisplayService.GetTypeDisplayInfo(
firstSymbol, directStructuralTypes.ToImmutableArrayOrEmpty(), _semanticModel, _position);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,6 @@ protected static SignatureHelpItem CreateItemImpl(
IList<SignatureHelpSymbolParameter> parameters,
IList<SymbolDisplayPart>? descriptionParts)
{
prefixParts = structuralTypeDisplayService.InlineDelegateAnonymousTypes(prefixParts, semanticModel, position);
separatorParts = structuralTypeDisplayService.InlineDelegateAnonymousTypes(separatorParts, semanticModel, position);
suffixParts = structuralTypeDisplayService.InlineDelegateAnonymousTypes(suffixParts, semanticModel, position);
parameters = parameters.Select(p => InlineDelegateAnonymousTypes(p, semanticModel, position, structuralTypeDisplayService)).ToList();
descriptionParts = descriptionParts == null
? SpecializedCollections.EmptyList<SymbolDisplayPart>()
: descriptionParts;
Expand All @@ -187,20 +183,20 @@ protected static SignatureHelpItem CreateItemImpl(

var structuralTypes =
from part in allParts
where part.Symbol.IsNormalAnonymousType() || part.Symbol.IsTupleType()
where part.Symbol.IsAnonymousType() || part.Symbol.IsTupleType()
select (INamedTypeSymbol)part.Symbol!;

var info = structuralTypeDisplayService.GetTypeDisplayInfo(
orderSymbol, structuralTypes.ToImmutableArray(), semanticModel, position);

if (info.TypesParts.Count > 0)
{
var anonymousTypeParts = new List<SymbolDisplayPart>
var structuralTypeParts = new List<SymbolDisplayPart>
{
new SymbolDisplayPart(SymbolDisplayPartKind.Space, null, "\r\n\r\n")
};

anonymousTypeParts.AddRange(info.TypesParts);
structuralTypeParts.AddRange(info.TypesParts);

return new SymbolKeySignatureHelpItem(
orderSymbol,
Expand All @@ -210,7 +206,7 @@ where part.Symbol.IsNormalAnonymousType() || part.Symbol.IsTupleType()
info.ReplaceStructuralTypes(separatorParts, semanticModel, position).ToTaggedText(),
info.ReplaceStructuralTypes(suffixParts, semanticModel, position).ToTaggedText(),
parameters.Select(p => ReplaceStructuralTypes(p, info, semanticModel, position)).Select(p => (SignatureHelpParameter)p),
anonymousTypeParts.ToTaggedText());
structuralTypeParts.ToTaggedText());
}

return new SymbolKeySignatureHelpItem(
Expand Down Expand Up @@ -238,22 +234,6 @@ private static SignatureHelpSymbolParameter ReplaceStructuralTypes(
info.ReplaceStructuralTypes(parameter.SelectedDisplayParts, semanticModel, position));
}

private static SignatureHelpSymbolParameter InlineDelegateAnonymousTypes(
SignatureHelpSymbolParameter parameter,
SemanticModel semanticModel,
int position,
IStructuralTypeDisplayService structuralTypeDisplayService)
{
return new SignatureHelpSymbolParameter(
parameter.Name,
parameter.IsOptional,
parameter.DocumentationFactory,
structuralTypeDisplayService.InlineDelegateAnonymousTypes(parameter.DisplayParts, semanticModel, position),
structuralTypeDisplayService.InlineDelegateAnonymousTypes(parameter.PrefixDisplayParts, semanticModel, position),
structuralTypeDisplayService.InlineDelegateAnonymousTypes(parameter.SuffixDisplayParts, semanticModel, position),
structuralTypeDisplayService.InlineDelegateAnonymousTypes(parameter.SelectedDisplayParts, semanticModel, position));
}

public async Task<SignatureHelpItems?> GetItemsAsync(
Document document, int position, SignatureHelpTriggerInfo triggerInfo, SignatureHelpOptions options, CancellationToken cancellationToken)
{
Expand Down
Loading