Skip to content

Commit

Permalink
Merge pull request #58908 from CyrusNajmabadi/anonDelDisplay
Browse files Browse the repository at this point in the history
Show a specialized display for anonymous delegates in C#
  • Loading branch information
CyrusNajmabadi authored Jan 18, 2022
2 parents 33ff264 + b74aff4 commit ae335c2
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 214 deletions.
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(""));
}

[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

0 comments on commit ae335c2

Please sign in to comment.