File: LanguageServices\DeclaredSymbolFactoryService\AbstractDeclaredSymbolInfoFactoryService.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Threading;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.LanguageService;
 
internal abstract class AbstractDeclaredSymbolInfoFactoryService<
    TCompilationUnitSyntax,
    TUsingDirectiveSyntax,
    TNamespaceDeclarationSyntax,
    TTypeDeclarationSyntax,
    TEnumDeclarationSyntax,
    TMethodDeclarationSyntax,
    TMemberDeclarationSyntax,
    TNameSyntax,
    TQualifiedNameSyntax,
    TIdentifierNameSyntax> : IDeclaredSymbolInfoFactoryService
    where TCompilationUnitSyntax : SyntaxNode
    where TUsingDirectiveSyntax : SyntaxNode
    where TNamespaceDeclarationSyntax : TMemberDeclarationSyntax
    where TTypeDeclarationSyntax : TMemberDeclarationSyntax
    where TEnumDeclarationSyntax : TMemberDeclarationSyntax
    where TMethodDeclarationSyntax : TMemberDeclarationSyntax
    where TMemberDeclarationSyntax : SyntaxNode
    where TNameSyntax : SyntaxNode
    where TQualifiedNameSyntax : TNameSyntax
    where TIdentifierNameSyntax : TNameSyntax
{
    private static readonly ObjectPool<List<Dictionary<string, string>>> s_aliasMapListPool
        = SharedPools.Default<List<Dictionary<string, string>>>();
 
    // Note: these names are stored case insensitively.  That way the alias mapping works 
    // properly for VB.  It will mean that our inheritance maps may store more links in them
    // for C#.  However, that's ok.  It will be rare in practice, and all it means is that
    // we'll end up examining slightly more types (likely 0) when doing operations like 
    // Find all references.
    private static readonly ObjectPool<Dictionary<string, string>> s_aliasMapPool
        = SharedPools.StringIgnoreCaseDictionary<string>();
 
    protected AbstractDeclaredSymbolInfoFactoryService()
    {
    }
 
    protected abstract SyntaxList<TMemberDeclarationSyntax> GetChildren(TCompilationUnitSyntax node);
    protected abstract SyntaxList<TMemberDeclarationSyntax> GetChildren(TNamespaceDeclarationSyntax node);
    protected abstract SyntaxList<TMemberDeclarationSyntax> GetChildren(TTypeDeclarationSyntax node);
    protected abstract IEnumerable<TMemberDeclarationSyntax> GetChildren(TEnumDeclarationSyntax node);
 
    protected abstract SyntaxList<TUsingDirectiveSyntax> GetUsingAliases(TCompilationUnitSyntax node);
    protected abstract SyntaxList<TUsingDirectiveSyntax> GetUsingAliases(TNamespaceDeclarationSyntax node);
 
    protected abstract TNameSyntax GetName(TNamespaceDeclarationSyntax node);
    protected abstract TNameSyntax GetLeft(TQualifiedNameSyntax node);
    protected abstract TNameSyntax GetRight(TQualifiedNameSyntax node);
    protected abstract SyntaxToken GetIdentifier(TIdentifierNameSyntax node);
 
    protected abstract string GetContainerDisplayName(TMemberDeclarationSyntax namespaceDeclaration);
    protected abstract string GetFullyQualifiedContainerName(TMemberDeclarationSyntax memberDeclaration, string rootNamespace);
 
    protected abstract DeclaredSymbolInfo? GetTypeDeclarationInfo(
        SyntaxNode container, TTypeDeclarationSyntax typeDeclaration, StringTable stringTable, string containerDisplayName, string fullyQualifiedContainerName);
    protected abstract DeclaredSymbolInfo GetEnumDeclarationInfo(
        SyntaxNode container, TEnumDeclarationSyntax enumDeclaration, StringTable stringTable, string containerDisplayName, string fullyQualifiedContainerName);
    protected abstract void AddMemberDeclarationInfos(
        SyntaxNode container, TMemberDeclarationSyntax memberDeclaration, StringTable stringTable, ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos, string containerDisplayName, string fullyQualifiedContainerName);
    protected abstract void AddLocalFunctionInfos(
        TMemberDeclarationSyntax memberDeclaration, StringTable stringTable, ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos, string containerDisplayName, string fullyQualifiedContainerName, CancellationToken cancellationToken);
    protected abstract void AddSynthesizedDeclaredSymbolInfos(
        SyntaxNode container, TMemberDeclarationSyntax memberDeclaration, StringTable stringTable, ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos, string containerDisplayName, string fullyQualifiedContainerName, CancellationToken cancellationToken);
 
    /// <summary>
    /// Get the name of the target type of specified extension method declaration. The node provided must be an
    /// extension method declaration,  i.e. calling `TryGetDeclaredSymbolInfo()` on `node` should return a
    /// `DeclaredSymbolInfo` of kind `ExtensionMethod`. If the return value is null, then it means this is a
    /// "complex" method (as described at <see cref="TopLevelSyntaxTreeIndex.ExtensionMethodInfo"/>).
    /// </summary>
    protected abstract string GetReceiverTypeName(TMethodDeclarationSyntax node);
    protected abstract bool TryGetAliasesFromUsingDirective(TUsingDirectiveSyntax node, out ImmutableArray<(string aliasName, string name)> aliases);
    protected abstract string GetRootNamespace(CompilationOptions compilationOptions);
 
    protected static List<Dictionary<string, string>> AllocateAliasMapList()
        => s_aliasMapListPool.Allocate();
 
    // We do not differentiate arrays of different kinds for simplicity.
    // e.g. int[], int[][], int[,], etc. are all represented as int[] in the index.
    protected static string CreateReceiverTypeString(string typeName, bool isArray)
    {
        if (string.IsNullOrEmpty(typeName))
        {
            return isArray
                ? FindSymbols.Extensions.ComplexArrayReceiverTypeName
                : FindSymbols.Extensions.ComplexReceiverTypeName;
        }
        else
        {
            return isArray
                ? typeName + FindSymbols.Extensions.ArrayReceiverTypeNameSuffix
                : typeName;
        }
    }
 
    protected static string CreateValueTupleTypeString(int elementCount)
    {
        const string ValueTupleName = "ValueTuple";
        if (elementCount == 0)
        {
            return ValueTupleName;
        }
        // A ValueTuple can have up to 8 type parameters.
        return ValueTupleName + ArityUtilities.GetMetadataAritySuffix(elementCount > 8 ? 8 : elementCount);
    }
 
    protected static void FreeAliasMapList(List<Dictionary<string, string>> list)
    {
        if (list != null)
        {
            foreach (var aliasMap in list)
            {
                FreeAliasMap(aliasMap);
            }
 
            s_aliasMapListPool.ClearAndFree(list);
        }
    }
 
    protected static void FreeAliasMap(Dictionary<string, string> aliasMap)
    {
        if (aliasMap != null)
        {
            s_aliasMapPool.ClearAndFree(aliasMap);
        }
    }
 
    protected static Dictionary<string, string> AllocateAliasMap()
        => s_aliasMapPool.Allocate();
 
    protected static void Intern(StringTable stringTable, ArrayBuilder<string> builder)
    {
        for (int i = 0, n = builder.Count; i < n; i++)
        {
            builder[i] = stringTable.Add(builder[i]);
        }
    }
 
    public void AddDeclaredSymbolInfos(
        ProjectState project,
        SyntaxNode root,
        ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos,
        Dictionary<string, ArrayBuilder<int>> extensionMethodInfo,
        CancellationToken cancellationToken)
    {
        var stringTable = SyntaxTreeIndex.GetStringTable(project);
        var rootNamespace = this.GetRootNamespace(project.CompilationOptions!);
 
        using var _1 = PooledDictionary<string, string?>.GetInstance(out var aliases);
 
        foreach (var usingAlias in GetUsingAliases((TCompilationUnitSyntax)root))
        {
            if (this.TryGetAliasesFromUsingDirective(usingAlias, out var current))
                AddAliases(aliases, current);
        }
 
        foreach (var child in GetChildren((TCompilationUnitSyntax)root))
            AddDeclaredSymbolInfos(root, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo, "", "", cancellationToken);
    }
 
    private void AddDeclaredSymbolInfos(
        SyntaxNode container,
        TMemberDeclarationSyntax memberDeclaration,
        StringTable stringTable,
        string rootNamespace,
        ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos,
        Dictionary<string, string?> aliases,
        Dictionary<string, ArrayBuilder<int>> extensionMethodInfo,
        string containerDisplayName,
        string fullyQualifiedContainerName,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
 
        if (memberDeclaration is TNamespaceDeclarationSyntax namespaceDeclaration)
        {
            AddNamespaceDeclaredSymbolInfos(GetName(namespaceDeclaration), fullyQualifiedContainerName);
 
            var innerContainerDisplayName = GetContainerDisplayName(memberDeclaration);
 
            foreach (var usingAlias in GetUsingAliases(namespaceDeclaration))
            {
                if (this.TryGetAliasesFromUsingDirective(usingAlias, out var current))
                    AddAliases(aliases, current);
            }
 
            var innerFullyQualifiedContainerName = GetFullyQualifiedContainerName(memberDeclaration, rootNamespace);
            foreach (var child in GetChildren(namespaceDeclaration))
            {
                AddDeclaredSymbolInfos(
                    memberDeclaration, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo,
                    innerContainerDisplayName, innerFullyQualifiedContainerName, cancellationToken);
            }
        }
        else if (memberDeclaration is TTypeDeclarationSyntax typeDeclaration)
        {
            var innerContainerDisplayName = GetContainerDisplayName(memberDeclaration);
 
            // Add the item for the type itself:
            declaredSymbolInfos.AddIfNotNull(GetTypeDeclarationInfo(
                container,
                typeDeclaration,
                stringTable,
                containerDisplayName,
                fullyQualifiedContainerName));
 
            // Then any synthesized members in that type (for example, synthesized properties in a record):
            var innerFullyQualifiedContainerName = GetFullyQualifiedContainerName(memberDeclaration, rootNamespace);
            AddSynthesizedDeclaredSymbolInfos(
                container,
                memberDeclaration,
                stringTable,
                declaredSymbolInfos,
                innerContainerDisplayName,
                innerFullyQualifiedContainerName,
                cancellationToken);
 
            // Then recurse into the children and add those.
            foreach (var child in GetChildren(typeDeclaration))
            {
                AddDeclaredSymbolInfos(
                    memberDeclaration, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo,
                    innerContainerDisplayName, innerFullyQualifiedContainerName, cancellationToken);
            }
        }
        else if (memberDeclaration is TEnumDeclarationSyntax enumDeclaration)
        {
            var innerContainerDisplayName = GetContainerDisplayName(memberDeclaration);
 
            // Add the item for the type itself:
            declaredSymbolInfos.Add(GetEnumDeclarationInfo(
                container,
                enumDeclaration,
                stringTable,
                containerDisplayName,
                fullyQualifiedContainerName));
 
            // Then recurse into the children and add those.
            var innerFullyQualifiedContainerName = GetFullyQualifiedContainerName(memberDeclaration, rootNamespace);
            foreach (var child in GetChildren(enumDeclaration))
            {
                AddDeclaredSymbolInfos(
                    memberDeclaration, child, stringTable, rootNamespace, declaredSymbolInfos, aliases, extensionMethodInfo,
                    innerContainerDisplayName, innerFullyQualifiedContainerName, cancellationToken);
            }
        }
        else
        {
            // For anything that isn't a namespace/type/enum (generally a member), try to add the information about that
            var count = declaredSymbolInfos.Count;
            AddMemberDeclarationInfos(
                container,
                memberDeclaration,
                stringTable,
                declaredSymbolInfos,
                containerDisplayName,
                fullyQualifiedContainerName);
 
            // If the AddSingle call added an item, and that item was an extension method, then go and add the
            // information about this extension method to our 
            if (declaredSymbolInfos.Count != count &&
                declaredSymbolInfos.Last().Kind == DeclaredSymbolInfoKind.ExtensionMethod &&
                memberDeclaration is TMethodDeclarationSyntax methodDeclaration)
            {
                AddExtensionMethodInfo(methodDeclaration);
            }
 
            AddLocalFunctionInfos(
                memberDeclaration,
                stringTable,
                declaredSymbolInfos,
                containerDisplayName,
                fullyQualifiedContainerName,
                cancellationToken);
        }
 
        return;
 
        // Returns the new fully-qualified-container-name built from fullyQualifiedContainerName
        // with all the pieces of 'name' added to the end of it.
        string AddNamespaceDeclaredSymbolInfos(TNameSyntax name, string fullyQualifiedContainerName)
        {
            if (name is TQualifiedNameSyntax qualifiedName)
            {
                // Recurse down the left side of the qualified name.  Build up the new fully qualified
                // parent name for when going down the right side.
                var parentQualifiedContainerName = AddNamespaceDeclaredSymbolInfos(GetLeft(qualifiedName), fullyQualifiedContainerName);
                return AddNamespaceDeclaredSymbolInfos(GetRight(qualifiedName), parentQualifiedContainerName);
            }
            else if (name is TIdentifierNameSyntax nameSyntax)
            {
                var namespaceName = GetIdentifier(nameSyntax).ValueText;
                declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                    stringTable,
                    namespaceName,
                    nameSuffix: null,
                    containerDisplayName: null,
                    fullyQualifiedContainerName,
                    isPartial: true,
                    hasAttributes: false,
                    DeclaredSymbolInfoKind.Namespace,
                    Accessibility.Public,
                    nameSyntax.Span,
                    inheritanceNames: []));
 
                return string.IsNullOrEmpty(fullyQualifiedContainerName)
                    ? namespaceName
                    : fullyQualifiedContainerName + "." + namespaceName;
            }
            else
            {
                return fullyQualifiedContainerName;
            }
        }
 
        void AddExtensionMethodInfo(TMethodDeclarationSyntax methodDeclaration)
        {
            var declaredSymbolInfoIndex = declaredSymbolInfos.Count - 1;
 
            var receiverTypeName = this.GetReceiverTypeName(methodDeclaration);
 
            // Target type is an alias
            if (aliases.TryGetValue(receiverTypeName, out var originalName))
            {
                // it is an alias of multiple with identical name,
                // simply treat it as a complex method.
                if (originalName == null)
                {
                    receiverTypeName = FindSymbols.Extensions.ComplexReceiverTypeName;
                }
                else
                {
                    // replace the alias with its original name.
                    receiverTypeName = originalName;
                }
            }
 
            if (!extensionMethodInfo.TryGetValue(receiverTypeName, out var arrayBuilder))
            {
                arrayBuilder = ArrayBuilder<int>.GetInstance();
                extensionMethodInfo[receiverTypeName] = arrayBuilder;
            }
 
            arrayBuilder.Add(declaredSymbolInfoIndex);
        }
    }
 
    private static void AddAliases(Dictionary<string, string?> allAliases, ImmutableArray<(string aliasName, string name)> aliases)
    {
        foreach (var (aliasName, name) in aliases)
        {
            // In C#, it's valid to declare two alias with identical name,
            // as long as they are in different containers.
            //
            // e.g.
            //      using X = System.String;
            //      namespace N
            //      {
            //          using X = System.Int32;
            //      }
            //
            // If we detect this, we will simply treat extension methods whose
            // target type is this alias as complex method.
            if (allAliases.ContainsKey(aliasName))
            {
                allAliases[aliasName] = null;
            }
            else
            {
                allAliases[aliasName] = name;
            }
        }
    }
}