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 all 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,23 @@ 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 TestInferredNonAnonymousDelegateType1()
{
await TestAsync(
@"class C
{
void M()
{
$$var v = (int i) => i.ToString();
}
}",
MainDescription("delegate TResult System.Func<in T, out TResult>(T arg)"),
AnonymousTypes(""));
}

[WorkItem(58871, "https://github.com/dotnet/roslyn/issues/58871")]
[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task TestAnonymousSynthesizedLambdaType()
{
Expand All @@ -7925,7 +7942,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($"({FeaturesResources.local_variable}) 'a v"),
AnonymousTypes(
$@"
{FeaturesResources.Types_colon}
'a {FeaturesResources.is_} delegate string (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_} delegate string (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_} Delegate 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_} Delegate 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_} Delegate 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_} Delegate Function (i As Integer) As 'b" & vbCrLf &
$" 'b {FeaturesResources.is_} New With {{ .Sq As Integer, .M As 'c }}" & vbCrLf &
$" 'c {FeaturesResources.is_} Delegate 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 @@ -10,6 +10,7 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageServices;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
Expand All @@ -26,7 +27,9 @@ public CSharpStructuralTypeDisplayService()
{
}

public override ImmutableArray<SymbolDisplayPart> GetAnonymousTypeParts(
protected override ISyntaxFacts SyntaxFactsService => CSharpSyntaxFacts.Instance;

protected override ImmutableArray<SymbolDisplayPart> GetNormalAnonymousTypeParts(
INamedTypeSymbol anonymousType, SemanticModel semanticModel, int position)
{
using var _ = ArrayBuilder<SymbolDisplayPart>.GetInstance(out var members);
Expand Down
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 @@ -80,12 +80,19 @@ public override void VisitNamedType(INamedTypeSymbol symbol)

if (_seenTypes.Add(symbol))
{
if (symbol.IsNormalAnonymousType())
if (symbol.IsAnonymousType())
{
_namedTypes.Add(symbol, (order: _namedTypes.Count, count: 1));

foreach (var property in symbol.GetValidAnonymousTypeProperties())
property.Accept(this);
if (symbol.IsDelegateType())
{
symbol.DelegateInvokeMethod?.Accept(this);
}
else
{
foreach (var property in symbol.GetValidAnonymousTypeProperties())
property.Accept(this);
}
}
else if (symbol.IsTupleType)
{
Expand All @@ -94,10 +101,6 @@ public override void VisitNamedType(INamedTypeSymbol symbol)
foreach (var field in symbol.TupleElements)
field.Accept(this);
}
else if (symbol.IsAnonymousDelegateType())
{
symbol.DelegateInvokeMethod?.Accept(this);
}
else
{
foreach (var typeArgument in symbol.GetAllTypeArguments())
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,50 @@ 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);
private static readonly SymbolDisplayFormat s_delegateDisplay =
s_minimalWithoutExpandedTuples.WithMemberOptions(s_minimalWithoutExpandedTuples.MemberOptions & ~SymbolDisplayMemberOptions.IncludeContainingType);

protected abstract ISyntaxFacts SyntaxFactsService { get; }
protected abstract ImmutableArray<SymbolDisplayPart> GetNormalAnonymousTypeParts(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);

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

var invokeMethod = anonymousType.DelegateInvokeMethod ?? throw ExceptionUtilities.Unreachable;

parts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, symbol: null,
SyntaxFactsService.GetText(SyntaxFactsService.SyntaxKinds.DelegateKeyword)));
parts.AddRange(Space());
parts.AddRange(MassageDelegateParts(invokeMethod, invokeMethod.ToMinimalDisplayParts(
semanticModel, position, s_delegateDisplay)));

return parts.ToImmutable();
}

private static ImmutableArray<SymbolDisplayPart> MassageDelegateParts(
IMethodSymbol invokeMethod,
ImmutableArray<SymbolDisplayPart> parts)
{
using var _ = ArrayBuilder<SymbolDisplayPart>.GetInstance(out var result);

// Ugly hack. Remove the "Invoke" name the compiler layer adds to the parts.
foreach (var part in parts)
{
if (!Equals(invokeMethod, part.Symbol))
result.Add(part);
}

return result.ToImmutable();
}

public StructuralTypeDisplayInfo GetTypeDisplayInfo(
ISymbol orderSymbol,
Expand Down Expand Up @@ -47,9 +90,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 +110,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
Loading