File: LanguageServices\AnonymousTypeDisplayService\AbstractStructuralTypeDisplayService.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.LanguageService;
 
internal abstract partial class AbstractStructuralTypeDisplayService : IStructuralTypeDisplayService
{
    protected static readonly SymbolDisplayFormat s_minimalWithoutExpandedTuples = SymbolDisplayFormat.MinimallyQualifiedFormat.AddMiscellaneousOptions(
        SymbolDisplayMiscellaneousOptions.CollapseTupleTypes);
 
    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)
    {
        var invokeMethod = anonymousType.DelegateInvokeMethod ?? throw ExceptionUtilities.Unreachable();
 
        return
        [
            new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, symbol: null, SyntaxFactsService.GetText(SyntaxFactsService.SyntaxKinds.DelegateKeyword)),
            .. Space(),
            .. MassageDelegateParts(invokeMethod, invokeMethod.ToMinimalDisplayParts(semanticModel, position, s_delegateDisplay))
        ];
    }
 
    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.ToImmutableAndClear();
    }
 
    public StructuralTypeDisplayInfo GetTypeDisplayInfo(
        ISymbol orderSymbol,
        ImmutableArray<INamedTypeSymbol> directStructuralTypeReferences,
        SemanticModel semanticModel,
        int position)
    {
        if (directStructuralTypeReferences.Length == 0)
        {
            return new StructuralTypeDisplayInfo(
                SpecializedCollections.EmptyDictionary<INamedTypeSymbol, string>(),
                SpecializedCollections.EmptyList<SymbolDisplayPart>());
        }
 
        var transitiveStructuralTypeReferences = GetTransitiveStructuralTypeReferences(directStructuralTypeReferences);
        transitiveStructuralTypeReferences = OrderStructuralTypes(transitiveStructuralTypeReferences, orderSymbol);
 
        IList<SymbolDisplayPart> typeParts = [];
 
        if (transitiveStructuralTypeReferences.Length > 0)
        {
            typeParts.Add(PlainText(FeaturesResources.Types_colon));
            typeParts.AddRange(LineBreak());
        }
 
        for (var i = 0; i < transitiveStructuralTypeReferences.Length; i++)
        {
            if (i != 0)
            {
                typeParts.AddRange(LineBreak());
            }
 
            var structuralType = transitiveStructuralTypeReferences[i];
            typeParts.AddRange(Space(count: 4));
 
            var kind = structuralType.GetSymbolDisplayPartKind();
 
            typeParts.Add(Part(kind, structuralType, structuralType.Name));
            typeParts.AddRange(Space());
            typeParts.Add(PlainText(FeaturesResources.is_));
            typeParts.AddRange(Space());
 
            if (structuralType.IsValueType)
            {
                typeParts.AddRange(structuralType.ToMinimalDisplayParts(semanticModel, position));
            }
            else
            {
                typeParts.AddRange(GetAnonymousTypeParts(structuralType, semanticModel, position));
            }
        }
 
        // Finally, assign a name to all the anonymous types.
        var structuralTypeToName = GenerateStructuralTypeNames(transitiveStructuralTypeReferences);
        typeParts = StructuralTypeDisplayInfo.ReplaceStructuralTypes(
            typeParts, structuralTypeToName, semanticModel, position);
 
        return new StructuralTypeDisplayInfo(structuralTypeToName, typeParts);
    }
 
    private static Dictionary<INamedTypeSymbol, string> GenerateStructuralTypeNames(
        IList<INamedTypeSymbol> anonymousTypes)
    {
        var current = 0;
        var anonymousTypeToName = new Dictionary<INamedTypeSymbol, string>();
        foreach (var type in anonymousTypes)
        {
            anonymousTypeToName[type] = GenerateStructuralTypeName(current);
            current++;
        }
 
        return anonymousTypeToName;
    }
 
    private static string GenerateStructuralTypeName(int current)
    {
        var c = (char)('a' + current);
        if (c is >= 'a' and <= 'z')
        {
            return "'" + c.ToString();
        }
 
        return "'" + current.ToString();
    }
 
    private static ImmutableArray<INamedTypeSymbol> OrderStructuralTypes(
        ImmutableArray<INamedTypeSymbol> structuralTypes,
        ISymbol symbol)
    {
        if (symbol is IMethodSymbol method)
        {
            return [.. structuralTypes.OrderBy(
                (n1, n2) =>
                {
                    var index1 = method.TypeArguments.IndexOf(n1);
                    var index2 = method.TypeArguments.IndexOf(n2);
                    index1 = index1 < 0 ? int.MaxValue : index1;
                    index2 = index2 < 0 ? int.MaxValue : index2;
 
                    return index1 - index2;
                })];
        }
        else if (symbol is IPropertySymbol property)
        {
            return [.. structuralTypes.OrderBy(
                (n1, n2) =>
                {
                    if (n1.Equals(property.ContainingType) && !n2.Equals(property.ContainingType))
                    {
                        return -1;
                    }
                    else if (!n1.Equals(property.ContainingType) && n2.Equals(property.ContainingType))
                    {
                        return 1;
                    }
                    else
                    {
                        return 0;
                    }
                })];
        }
 
        return structuralTypes;
    }
 
    private static ImmutableArray<INamedTypeSymbol> GetTransitiveStructuralTypeReferences(
        ImmutableArray<INamedTypeSymbol> structuralTypes)
    {
        var transitiveReferences = new Dictionary<INamedTypeSymbol, (int order, int count)>();
        var visitor = new StructuralTypeCollectorVisitor(transitiveReferences);
 
        foreach (var type in structuralTypes)
            type.Accept(visitor);
 
        // If we have at least one tuple that showed up multiple times, then move *all* tuples to the 'Types:'
        // section to clean up the display.
        var hasAtLeastOneTupleWhichAppearsMultipleTimes = transitiveReferences.Any(kvp => kvp.Key.IsTupleType && kvp.Value.count >= 2);
 
        using var _ = ArrayBuilder<INamedTypeSymbol>.GetInstance(out var result);
 
        foreach (var (namedType, _) in transitiveReferences.OrderBy(kvp => kvp.Value.order))
        {
            if (namedType.IsTupleType && !hasAtLeastOneTupleWhichAppearsMultipleTimes)
                continue;
 
            result.Add(namedType);
        }
 
        return result.ToImmutableAndClear();
    }
 
    protected static IEnumerable<SymbolDisplayPart> LineBreak(int count = 1)
    {
        for (var i = 0; i < count; i++)
        {
            yield return new SymbolDisplayPart(SymbolDisplayPartKind.LineBreak, null, "\r\n");
        }
    }
 
    protected static SymbolDisplayPart PlainText(string text)
        => Part(SymbolDisplayPartKind.Text, text);
 
    private static SymbolDisplayPart Part(SymbolDisplayPartKind kind, string text)
        => Part(kind, null, text);
 
    private static SymbolDisplayPart Part(SymbolDisplayPartKind kind, ISymbol? symbol, string text)
        => new(kind, symbol, text);
 
    protected static IEnumerable<SymbolDisplayPart> Space(int count = 1)
    {
        for (var i = 0; i < count; i++)
        {
            yield return new SymbolDisplayPart(SymbolDisplayPartKind.Space, null, " ");
        }
    }
 
    protected static SymbolDisplayPart Punctuation(string text)
        => Part(SymbolDisplayPartKind.Punctuation, text);
 
    protected static SymbolDisplayPart Keyword(string text)
        => Part(SymbolDisplayPartKind.Keyword, text);
}