File: Shared\Extensions\ISymbolExtensions_Sorting.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.
 
#nullable disable
 
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using Microsoft.CodeAnalysis.LanguageService;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Extensions;
 
internal static partial class ISymbolExtensions2
{
    [Obsolete("Use overload without ISymbolDisplayService")]
    public static ImmutableArray<TSymbol> Sort<TSymbol>(
        this ImmutableArray<TSymbol> symbols,
        ISymbolDisplayService symbolDisplayService,
        SemanticModel semanticModel,
        int position)
        where TSymbol : ISymbol
    {
        return Sort(symbols, semanticModel, position);
    }
 
    public static ImmutableArray<TSymbol> Sort<TSymbol>(
        this ImmutableArray<TSymbol> symbols,
        SemanticModel semanticModel,
        int position)
        where TSymbol : ISymbol
    {
        var symbolToParameterTypeNames = new ConcurrentDictionary<TSymbol, string[]>();
        string[] getParameterTypeNames(TSymbol s) => GetParameterTypeNames(s, semanticModel, position);
 
        return [.. symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames))];
    }
 
    private static INamedTypeSymbol GetNamedType(ITypeSymbol type)
        => type switch
        {
            INamedTypeSymbol namedType => namedType,
            IArrayTypeSymbol arrayType => GetNamedType(arrayType.ElementType),
            IPointerTypeSymbol pointerType => GetNamedType(pointerType.PointedAtType),
            _ => null,
        };
 
    private static int CompareParameters(
        ImmutableArray<IParameterSymbol> xParameters, string[] xTypeNames,
        ImmutableArray<IParameterSymbol> yParameters, string[] yTypeNames)
    {
        // * Order by the number of parameters
        // * If the same number of parameters...
        //   * Sort alphabetically by parameter type name
        //   * Params parameters are sorted at the end
        //   * Parameter types with type parameters are after those
        if (xParameters.IsDefault || yParameters.IsDefault)
        {
            return xParameters.IsDefault && yParameters.IsDefault ? 0 :
                   xParameters.IsDefault ? -1 : 1;
        }
 
        var diff = xParameters.Length - yParameters.Length;
        if (diff != 0)
        {
            return diff;
        }
 
        var paramCount = xParameters.Length;
        for (var i = 0; i < paramCount; i++)
        {
            var xParam = xParameters[i];
            var yParam = yParameters[i];
 
            var xParamType = GetNamedType(xParam.Type);
            var yParamType = GetNamedType(yParam.Type);
            if (xParamType != null && yParamType != null)
            {
                diff = CompareNamedTypes(xParamType, yParamType);
                if (diff != 0)
                {
                    return diff;
                }
            }
 
            if (xParam.IsParams != yParam.IsParams)
            {
                return xParam.IsParams ? 1 : -1;
            }
 
            diff = CultureInfo.CurrentUICulture.CompareInfo.Compare(xTypeNames[i], yTypeNames[i], CompareOptions.StringSort);
            if (diff != 0)
            {
                return diff;
            }
        }
 
        return 0;
    }
 
    private static int CompareProperties(IPropertySymbol xProperty, string[] xTypeNames, IPropertySymbol yProperty, string[] yTypeNames)
        => CompareParameters(xProperty.Parameters, xTypeNames, yProperty.Parameters, yTypeNames);
 
    private static int CompareMethods(IMethodSymbol xMethod, string[] xTypeNames, IMethodSymbol yMethod, string[] yTypeNames)
    {
        // * Order by arity
        // * Order by parameters
 
        var diff = xMethod.Arity - yMethod.Arity;
        if (diff != 0)
        {
            return diff;
        }
 
        return CompareParameters(xMethod.Parameters, xTypeNames, yMethod.Parameters, yTypeNames);
    }
 
    private static int CompareEvents(IEventSymbol xEvent, string[] xTypeNames, IEventSymbol yEvent, string[] yTypeNames)
        => CompareParameters(GetMethodOrIndexerOrEventParameters(xEvent), xTypeNames, GetMethodOrIndexerOrEventParameters(yEvent), yTypeNames);
 
    private static int CompareNamedTypes(INamedTypeSymbol xNamedType, INamedTypeSymbol yNamedType)
    {
        // For named types, we sort on arity.
        return xNamedType.Arity - yNamedType.Arity;
    }
 
    private static string[] GetParameterTypeNames(
        ISymbol symbol,
        SemanticModel semanticModel,
        int position)
    {
        return GetMethodOrIndexerOrEventParameters(symbol)
                     .Select(p => p.Type.ToMinimalDisplayString(semanticModel, position))
                     .ToArray();
    }
 
    private static ImmutableArray<IParameterSymbol> GetMethodOrIndexerOrEventParameters(ISymbol symbol)
    {
        if (symbol is IEventSymbol ev)
        {
            var type = ev.Type as INamedTypeSymbol;
            if (type.IsDelegateType())
            {
                return type.DelegateInvokeMethod.Parameters;
            }
        }
 
        return symbol.GetParameters();
    }
 
    private static int Compare<TSymbol>(
        TSymbol s1, TSymbol s2,
        ConcurrentDictionary<TSymbol, string[]> symbolToParameterTypeNames,
        Func<TSymbol, string[]> getParameterTypeNames)
        where TSymbol : ISymbol
    {
        var symbol1ParameterTypeNames = symbolToParameterTypeNames.GetOrAdd(s1, getParameterTypeNames);
        var symbol2ParameterTypeNames = symbolToParameterTypeNames.GetOrAdd(s2, getParameterTypeNames);
 
        // Order named types before methods and properties, and methods before properties.
 
        if (s1.Kind == SymbolKind.NamedType || s2.Kind == SymbolKind.NamedType)
        {
            return s1.Kind == s2.Kind
                ? CompareNamedTypes((INamedTypeSymbol)s1, (INamedTypeSymbol)s2)
                : s1.Kind == SymbolKind.NamedType ? -1 : 1;
        }
 
        if (s1.Kind == SymbolKind.Method || s2.Kind == SymbolKind.Method)
        {
            return s1.Kind == s2.Kind
                ? CompareMethods((IMethodSymbol)s1, symbol1ParameterTypeNames, (IMethodSymbol)s2, symbol2ParameterTypeNames)
                : s1.Kind == SymbolKind.Method ? -1 : 1;
        }
 
        if (s1.Kind == SymbolKind.Property || s2.Kind == SymbolKind.Property)
        {
            return s1.Kind == s2.Kind
                ? CompareProperties((IPropertySymbol)s1, symbol1ParameterTypeNames, (IPropertySymbol)s2, symbol2ParameterTypeNames)
                : s1.Kind == SymbolKind.Property ? -1 : 1;
        }
 
        if (s1.Kind == SymbolKind.Event || s2.Kind == SymbolKind.Event)
        {
            return s1.Kind == s2.Kind
                ? CompareEvents((IEventSymbol)s1, symbol1ParameterTypeNames, (IEventSymbol)s2, symbol2ParameterTypeNames)
                : s1.Kind == SymbolKind.Event ? -1 : 1;
        }
 
        throw ExceptionUtilities.UnexpectedValue((s1.Kind, s2.Kind));
    }
}