File: DocumentationCommentId.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// APIs for constructing documentation comment id's, and finding symbols that match ids.
    /// </summary>
    public static class DocumentationCommentId
    {
        private class ListPool<T> : ObjectPool<List<T>>
        {
            public ListPool()
                : base(() => new List<T>(10), 10)
            { }
 
            public void ClearAndFree(List<T> list)
            {
                list.Clear();
                base.Free(list);
            }
 
            [Obsolete("Do not use Free, Use ClearAndFree instead.", error: true)]
            public new void Free(List<T> list)
            {
                throw new NotSupportedException();
            }
        }
 
        private static readonly ListPool<ISymbol> s_symbolListPool = new ListPool<ISymbol>();
        private static readonly ListPool<INamespaceOrTypeSymbol> s_namespaceOrTypeListPool = new ListPool<INamespaceOrTypeSymbol>();
 
        /// <summary>
        /// Creates an id string used by external documentation comment files to identify declarations of types,
        /// namespaces, methods, properties, etc.
        /// </summary>
        /// <exception cref="ArgumentNullException">If <paramref name="symbol"/> is <see langword="null"/>.</exception>
        /// <returns>The documentation comment Id for this symbol, if it can be created. <see langword="null"/> if it cannot be.</returns>
        public static string? CreateDeclarationId(ISymbol symbol)
        {
            if (symbol == null)
            {
                throw new ArgumentNullException(nameof(symbol));
            }
 
            var builder = PooledStringBuilder.GetInstance();
            var generator = new PrefixAndDeclarationGenerator(builder);
            generator.Visit(symbol);
 
            return generator.Failed ? null : builder.ToStringAndFree();
        }
 
        /// <summary>
        /// Creates an id string used to reference type symbols (not strictly declarations, includes arrays, pointers,
        /// type parameters, etc.).
        /// </summary>
        /// <exception cref="ArgumentNullException">If <paramref name="symbol"/> is <see langword="null"/>.</exception>
        public static string CreateReferenceId(ISymbol symbol)
        {
            if (symbol == null)
            {
                throw new ArgumentNullException(nameof(symbol));
            }
 
            if (symbol is INamespaceSymbol)
            {
                // This is very odd.  For anything other than a namespace, we defer to ReferenceGenerator, which just
                // spits out a reference.  But for a namespace, we spit out a declaration (which means we prefix with
                // `N:`).  None of the other paths do this.  This is likely a bug, but it appears as if it has never
                // been noticed.
                var result = CreateDeclarationId(symbol);
 
                // All namespaces should succeed at being converted into a declaration ID.
                RoslynDebug.AssertNotNull(result);
                return result;
            }
 
            var builder = PooledStringBuilder.GetInstance();
            var generator = new ReferenceGenerator(builder, typeParameterContext: null);
            generator.Visit(symbol);
            return builder.ToStringAndFree();
        }
 
        /// <summary>
        /// Gets all declaration symbols that match the declaration id string
        /// </summary>
        public static ImmutableArray<ISymbol> GetSymbolsForDeclarationId(string id, Compilation compilation)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
 
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
 
            var results = s_symbolListPool.Allocate();
            try
            {
                Parser.ParseDeclaredSymbolId(id, compilation, results);
                return results.ToImmutableArray();
            }
            finally
            {
                s_symbolListPool.ClearAndFree(results);
            }
        }
 
        /// <summary>
        /// Try to get all the declaration symbols that match the declaration id string.
        /// Returns true if at least one symbol matches.
        /// </summary>
        private static bool TryGetSymbolsForDeclarationId(string id, Compilation compilation, List<ISymbol> results)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
 
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
 
            if (results == null)
            {
                throw new ArgumentNullException(nameof(results));
            }
 
            return Parser.ParseDeclaredSymbolId(id, compilation, results);
        }
 
        /// <summary>
        /// Gets the first declaration symbol that matches the declaration id string, order undefined.
        /// </summary>
        public static ISymbol? GetFirstSymbolForDeclarationId(string id, Compilation compilation)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
 
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
 
            var results = s_symbolListPool.Allocate();
            try
            {
                Parser.ParseDeclaredSymbolId(id, compilation, results);
                return results.Count == 0 ? null : results[0];
            }
            finally
            {
                s_symbolListPool.ClearAndFree(results);
            }
        }
 
        /// <summary>
        /// Gets the symbols that match the reference id string.
        /// </summary>
        public static ImmutableArray<ISymbol> GetSymbolsForReferenceId(string id, Compilation compilation)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
 
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
 
            var results = s_symbolListPool.Allocate();
            try
            {
                TryGetSymbolsForReferenceId(id, compilation, results);
                return results.ToImmutableArray();
            }
            finally
            {
                s_symbolListPool.ClearAndFree(results);
            }
        }
 
        /// <summary>
        /// Try to get all symbols that match the reference id string.
        /// Returns true if at least one symbol matches.
        /// </summary>
        private static bool TryGetSymbolsForReferenceId(string id, Compilation compilation, List<ISymbol> results)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
 
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
 
            if (results == null)
            {
                throw new ArgumentNullException(nameof(results));
            }
 
            if (id.Length > 1 && id[0] == 'N' && id[1] == ':')
            {
                return TryGetSymbolsForDeclarationId(id, compilation, results);
            }
 
            return Parser.ParseReferencedSymbolId(id, compilation, results);
        }
 
        /// <summary>
        /// Gets the first symbol that matches the reference id string, order undefined.
        /// </summary>
        public static ISymbol? GetFirstSymbolForReferenceId(string id, Compilation compilation)
        {
            if (id == null)
            {
                throw new ArgumentNullException(nameof(id));
            }
 
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
 
            if (id.Length > 1 && id[0] == 'N' && id[1] == ':')
            {
                return GetFirstSymbolForDeclarationId(id, compilation);
            }
 
            var results = s_symbolListPool.Allocate();
            try
            {
                Parser.ParseReferencedSymbolId(id, compilation, results);
                return results.Count == 0 ? null : results[0];
            }
            finally
            {
                s_symbolListPool.ClearAndFree(results);
            }
        }
 
        private static int GetTotalTypeParameterCount(INamedTypeSymbol? symbol)
        {
            int n = 0;
            while (symbol != null)
            {
                n += symbol.TypeParameters.Length;
                symbol = symbol.ContainingSymbol as INamedTypeSymbol;
            }
 
            return n;
        }
 
        // encodes dots with alternate # character
        private static string EncodeName(string name)
        {
            if (name.IndexOf('.') >= 0)
            {
                return name.Replace('.', '#');
            }
 
            return name;
        }
 
        private static string EncodePropertyName(string name)
        {
            // convert C# indexer names to 'Item'
            if (name == "this[]")
            {
                name = "Item";
            }
            else if (name.EndsWith(".this[]"))
            {
                name = name.Substring(0, name.Length - 6) + "Item";
            }
 
            return name;
        }
 
        private static string DecodePropertyName(string name, string language)
        {
            // special case, csharp names indexers 'this[]', not 'Item'
            if (language == LanguageNames.CSharp)
            {
                if (name == "Item")
                {
                    name = "this[]";
                }
                else if (name.EndsWith(".Item"))
                {
                    name = name.Substring(0, name.Length - 4) + "this[]";
                }
            }
 
            return name;
        }
 
        /// <summary>
        /// Callers should only call into <see cref="SymbolVisitor{TResult}.Visit(ISymbol?)"/> and should check <see
        /// cref="Failed"/> to see if it failed (in the case of an arbitrary symbol) or that it produced an expected
        /// value (in the case a known symbol type was used).
        /// </summary>
        /// <remarks>
        /// This will always succeed for a <see cref="INamespaceSymbol"/> or <see cref="INamedTypeSymbol"/>.  It may not
        /// succeed for other symbols.
        /// <para/> Once used, an instance of this visitor should be discarded.  Specifically it is stateful, and will
        /// stay in the failed state once it transitions there.
        /// </remarks>
        private sealed class PrefixAndDeclarationGenerator : SymbolVisitor
        {
            private readonly StringBuilder _builder;
            private readonly DeclarationGenerator _generator;
 
            private bool _failed;
 
            public PrefixAndDeclarationGenerator(StringBuilder builder)
            {
                _builder = builder;
                _generator = new DeclarationGenerator(builder);
            }
 
            /// <summary>
            /// If we hit anything we don't know about, indicate failure.
            /// </summary>
            public override void DefaultVisit(ISymbol symbol)
            {
                _failed = true;
            }
 
            public bool Failed => _failed || _generator.Failed;
 
            public override void VisitEvent(IEventSymbol symbol)
            {
                _builder.Append("E:");
                _generator.Visit(symbol);
            }
 
            public override void VisitField(IFieldSymbol symbol)
            {
                _builder.Append("F:");
                _generator.Visit(symbol);
            }
 
            public override void VisitProperty(IPropertySymbol symbol)
            {
                _builder.Append("P:");
                _generator.Visit(symbol);
            }
 
            public override void VisitMethod(IMethodSymbol symbol)
            {
                _builder.Append("M:");
                _generator.Visit(symbol);
            }
 
            public override void VisitNamespace(INamespaceSymbol symbol)
            {
                _builder.Append("N:");
                _generator.Visit(symbol);
            }
 
            public override void VisitNamedType(INamedTypeSymbol symbol)
            {
                _builder.Append("T:");
                _generator.Visit(symbol);
            }
 
            private sealed class DeclarationGenerator : SymbolVisitor<bool>
            {
                private readonly StringBuilder _builder;
                private ReferenceGenerator? _referenceGenerator;
 
                public bool Failed;
 
                public DeclarationGenerator(StringBuilder builder)
                {
                    _builder = builder;
                }
 
                private ReferenceGenerator GetReferenceGenerator(ISymbol typeParameterContext)
                {
                    if (_referenceGenerator == null || _referenceGenerator.TypeParameterContext != typeParameterContext)
                    {
                        _referenceGenerator = new ReferenceGenerator(_builder, typeParameterContext);
                    }
 
                    return _referenceGenerator;
                }
 
                /// <summary>
                /// If we hit anything we don't know about, indicate failure.
                /// </summary>
                public override bool DefaultVisit(ISymbol symbol)
                {
                    Failed = true;
                    return true;
                }
 
                public override bool VisitEvent(IEventSymbol symbol)
                {
                    if (this.Visit(symbol.ContainingSymbol))
                    {
                        _builder.Append('.');
                    }
 
                    _builder.Append(EncodeName(symbol.Name));
                    return true;
                }
 
                public override bool VisitField(IFieldSymbol symbol)
                {
                    if (this.Visit(symbol.ContainingSymbol))
                    {
                        _builder.Append('.');
                    }
 
                    _builder.Append(EncodeName(symbol.Name));
                    return true;
                }
 
                public override bool VisitProperty(IPropertySymbol symbol)
                {
                    if (this.Visit(symbol.ContainingSymbol))
                    {
                        _builder.Append('.');
                    }
 
                    var name = EncodePropertyName(symbol.Name);
                    _builder.Append(EncodeName(name));
 
                    AppendParameters(symbol.Parameters);
 
                    return true;
                }
 
                public override bool VisitMethod(IMethodSymbol symbol)
                {
                    if (this.Visit(symbol.ContainingSymbol))
                    {
                        _builder.Append('.');
                        _builder.Append(EncodeName(symbol.Name));
                    }
 
                    if (symbol.TypeParameters.Length > 0)
                    {
                        _builder.Append("``");
                        _builder.Append(symbol.TypeParameters.Length);
                    }
 
                    AppendParameters(symbol.Parameters);
 
                    if (!symbol.ReturnsVoid)
                    {
                        _builder.Append('~');
                        this.GetReferenceGenerator(symbol).Visit(symbol.ReturnType);
                    }
 
                    return true;
                }
 
                private void AppendParameters(ImmutableArray<IParameterSymbol> parameters)
                {
                    if (parameters.Length > 0)
                    {
                        _builder.Append('(');
 
                        for (int i = 0, n = parameters.Length; i < n; i++)
                        {
                            if (i > 0)
                            {
                                _builder.Append(',');
                            }
 
                            var p = parameters[i];
                            this.GetReferenceGenerator(p.ContainingSymbol).Visit(p.Type);
                            if (p.RefKind != RefKind.None)
                            {
                                _builder.Append('@');
                            }
                        }
 
                        _builder.Append(')');
                    }
                }
 
                public override bool VisitNamespace(INamespaceSymbol symbol)
                {
                    if (symbol.IsGlobalNamespace)
                    {
                        return false;
                    }
 
                    if (this.Visit(symbol.ContainingSymbol))
                    {
                        _builder.Append('.');
                    }
 
                    _builder.Append(EncodeName(symbol.Name));
                    return true;
                }
 
                public override bool VisitNamedType(INamedTypeSymbol symbol)
                {
                    if (this.Visit(symbol.ContainingSymbol))
                    {
                        _builder.Append('.');
                    }
 
                    _builder.Append(EncodeName(symbol.Name));
 
                    if (symbol.TypeParameters.Length > 0)
                    {
                        _builder.Append('`');
                        _builder.Append(symbol.TypeParameters.Length);
                    }
 
                    return true;
                }
            }
        }
 
        private class ReferenceGenerator : SymbolVisitor<bool>
        {
            private readonly StringBuilder _builder;
            private readonly ISymbol? _typeParameterContext;
 
            public ReferenceGenerator(StringBuilder builder, ISymbol? typeParameterContext)
            {
                _builder = builder;
                _typeParameterContext = typeParameterContext;
            }
 
            public ISymbol? TypeParameterContext
            {
                get { return _typeParameterContext; }
            }
 
            private void BuildDottedName(ISymbol symbol)
            {
                if (this.Visit(symbol.ContainingSymbol))
                {
                    _builder.Append('.');
                }
 
                _builder.Append(EncodeName(symbol.Name));
            }
 
            public override bool VisitAlias(IAliasSymbol symbol)
            {
                return symbol.Target.Accept(this);
            }
 
            public override bool VisitNamespace(INamespaceSymbol symbol)
            {
                if (symbol.IsGlobalNamespace)
                {
                    return false;
                }
 
                this.BuildDottedName(symbol);
                return true;
            }
 
            public override bool VisitNamedType(INamedTypeSymbol symbol)
            {
                this.BuildDottedName(symbol);
 
                if (symbol.IsGenericType)
                {
                    if (symbol.OriginalDefinition == symbol)
                    {
                        _builder.Append('`');
                        _builder.Append(symbol.TypeParameters.Length);
                    }
                    else if (symbol.TypeArguments.Length > 0)
                    {
                        _builder.Append('{');
 
                        for (int i = 0, n = symbol.TypeArguments.Length; i < n; i++)
                        {
                            if (i > 0)
                            {
                                _builder.Append(',');
                            }
 
                            this.Visit(symbol.TypeArguments[i]);
                        }
 
                        _builder.Append('}');
                    }
                }
 
                return true;
            }
 
            public override bool VisitDynamicType(IDynamicTypeSymbol symbol)
            {
                _builder.Append("System.Object");
 
                return true;
            }
 
            public override bool VisitArrayType(IArrayTypeSymbol symbol)
            {
                this.Visit(symbol.ElementType);
 
                _builder.Append('[');
 
                for (int i = 0, n = symbol.Rank; i < n; i++)
                {
                    // TODO: bounds info goes here
 
                    if (i > 0)
                    {
                        _builder.Append(',');
                    }
                }
 
                _builder.Append(']');
 
                return true;
            }
 
            public override bool VisitPointerType(IPointerTypeSymbol symbol)
            {
                this.Visit(symbol.PointedAtType);
                _builder.Append('*');
                return true;
            }
 
            public override bool VisitTypeParameter(ITypeParameterSymbol symbol)
            {
                if (!IsInScope(symbol))
                {
                    // reference to type parameter not in scope, make explicit scope reference
 
                    // Containing symbol may be null in error cases.
                    Debug.Assert(symbol.ContainingSymbol is null or INamedTypeSymbol or IMethodSymbol);
                    var declarer = new PrefixAndDeclarationGenerator(_builder);
                    declarer.Visit(symbol.ContainingSymbol);
                    Debug.Assert(!declarer.Failed);
 
                    _builder.Append(':');
                }
 
                if (symbol.DeclaringMethod != null)
                {
                    _builder.Append("``");
                    _builder.Append(symbol.Ordinal);
                }
                else
                {
                    // get count of all type parameter preceding the declaration of the type parameters containing symbol.
                    var container = symbol.ContainingSymbol?.ContainingSymbol;
                    var b = GetTotalTypeParameterCount(container as INamedTypeSymbol);
                    _builder.Append('`');
                    _builder.Append(b + symbol.Ordinal);
                }
 
                return true;
            }
 
            private bool IsInScope(ITypeParameterSymbol typeParameterSymbol)
            {
                // determine if the type parameter is declared in scope defined by the typeParameterContext symbol
                var typeParameterDeclarer = typeParameterSymbol.ContainingSymbol;
 
                for (var scope = _typeParameterContext; scope != null; scope = scope.ContainingSymbol)
                {
                    if (scope == typeParameterDeclarer)
                    {
                        return true;
                    }
                }
 
                return false;
            }
        }
 
        private static class Parser
        {
            public static bool ParseDeclaredSymbolId(string id, Compilation compilation, List<ISymbol> results)
            {
                if (id == null)
                {
                    return false;
                }
 
                if (id.Length < 2)
                {
                    return false;
                }
 
                int index = 0;
                results.Clear();
                ParseDeclaredId(id, ref index, compilation, results);
                return results.Count > 0;
            }
 
            // only supports type symbols
            public static bool ParseReferencedSymbolId(string id, Compilation compilation, List<ISymbol> results)
            {
                if (id == null)
                {
                    return false;
                }
 
                int index = 0;
                results.Clear();
                ParseTypeSymbol(id, ref index, compilation, null, results);
                return results.Count > 0;
            }
 
            private static void ParseDeclaredId(string id, ref int index, Compilation compilation, List<ISymbol> results)
            {
                var kindChar = PeekNextChar(id, index);
                SymbolKind kind;
 
                switch (kindChar)
                {
                    case 'E':
                        kind = SymbolKind.Event;
                        break;
                    case 'F':
                        kind = SymbolKind.Field;
                        break;
                    case 'M':
                        kind = SymbolKind.Method;
                        break;
                    case 'N':
                        kind = SymbolKind.Namespace;
                        break;
                    case 'P':
                        kind = SymbolKind.Property;
                        break;
                    case 'T':
                        kind = SymbolKind.NamedType;
                        break;
                    default:
                        // Documentation comment id must start with E, F, M, N, P or T
                        return;
                }
 
                index++;
                if (PeekNextChar(id, index) == ':')
                {
                    index++;
                }
 
                var containers = s_namespaceOrTypeListPool.Allocate();
                try
                {
                    containers.Add(compilation.GlobalNamespace);
 
                    string name;
                    int arity;
 
                    // process dotted names
                    while (true)
                    {
                        name = ParseName(id, ref index);
                        arity = 0;
 
                        // has type parameters?
                        if (PeekNextChar(id, index) == '`')
                        {
                            index++;
 
                            // method type parameters?
                            if (PeekNextChar(id, index) == '`')
                            {
                                index++;
                            }
 
                            arity = ReadNextInteger(id, ref index);
                        }
 
                        if (PeekNextChar(id, index) == '.')
                        {
                            // must be a namespace or type since name continues after dot
                            index++;
 
                            if (arity > 0)
                            {
                                // only types have arity
                                GetMatchingTypes(containers, name, arity, results);
                            }
                            else if (kind == SymbolKind.Namespace)
                            {
                                // if the results kind is namespace, then all dotted names must be namespaces
                                GetMatchingNamespaces(containers, name, results);
                            }
                            else
                            {
                                // could be either
                                GetMatchingNamespaceOrTypes(containers, name, results);
                            }
 
                            if (results.Count == 0)
                            {
                                // no matches found before dot, cannot continue.
                                return;
                            }
 
                            // results become the new containers
                            containers.Clear();
                            containers.AddRange(results.OfType<INamespaceOrTypeSymbol>());
                            results.Clear();
                        }
                        else
                        {
                            // no more dots, so don't loop any more
                            break;
                        }
                    }
 
                    switch (kind)
                    {
                        case SymbolKind.Method:
                            GetMatchingMethods(id, ref index, containers, name, arity, compilation, results);
                            break;
                        case SymbolKind.NamedType:
                            GetMatchingTypes(containers, name, arity, results);
                            break;
                        case SymbolKind.Property:
                            GetMatchingProperties(id, ref index, containers, name, compilation, results);
                            break;
                        case SymbolKind.Event:
                            GetMatchingEvents(containers, name, results);
                            break;
                        case SymbolKind.Field:
                            GetMatchingFields(containers, name, results);
                            break;
                        case SymbolKind.Namespace:
                            GetMatchingNamespaces(containers, name, results);
                            break;
                    }
                }
                finally
                {
                    s_namespaceOrTypeListPool.ClearAndFree(containers);
                }
            }
 
            private static ITypeSymbol? ParseTypeSymbol(string id, ref int index, Compilation compilation, ISymbol? typeParameterContext)
            {
                var results = s_symbolListPool.Allocate();
                try
                {
                    ParseTypeSymbol(id, ref index, compilation, typeParameterContext, results);
                    if (results.Count == 0)
                    {
                        return null;
                    }
                    else
                    {
                        return (ITypeSymbol)results[0];
                    }
                }
                finally
                {
                    s_symbolListPool.ClearAndFree(results);
                }
            }
 
            private static void ParseTypeSymbol(string id, ref int index, Compilation compilation, ISymbol? typeParameterContext, List<ISymbol> results)
            {
                var ch = PeekNextChar(id, index);
 
                // context expression embedded in reference => <context-definition>:<type-parameter>
                // note: this is a deviation from the language spec
                if ((ch == 'M' || ch == 'T') && PeekNextChar(id, index + 1) == ':')
                {
                    var contexts = s_symbolListPool.Allocate();
                    try
                    {
                        ParseDeclaredId(id, ref index, compilation, contexts);
                        if (contexts.Count == 0)
                        {
                            // context cannot be bound, so abort
                            return;
                        }
 
                        if (PeekNextChar(id, index) == ':')
                        {
                            index++;
 
                            // try parsing following in all contexts
                            var startIndex = index;
                            foreach (var context in contexts)
                            {
                                index = startIndex;
                                ParseTypeSymbol(id, ref index, compilation, context, results);
                            }
                        }
                        else
                        {
                            // this was a definition where we expected a reference?
                            results.AddRange(contexts.OfType<ITypeSymbol>());
                        }
                    }
                    finally
                    {
                        s_symbolListPool.ClearAndFree(contexts);
                    }
                }
                else
                {
                    if (ch == '`')
                    {
                        ParseTypeParameterSymbol(id, ref index, typeParameterContext, results);
                    }
                    else
                    {
                        ParseNamedTypeSymbol(id, ref index, compilation, typeParameterContext, results);
                    }
 
                    // apply any array or pointer constructions to results
                    var startIndex = index;
                    var endIndex = index;
 
                    for (int i = 0; i < results.Count; i++)
                    {
                        index = startIndex;
                        var typeSymbol = (ITypeSymbol)results[i];
 
                        while (true)
                        {
                            if (PeekNextChar(id, index) == '[')
                            {
                                var bounds = ParseArrayBounds(id, ref index);
                                typeSymbol = compilation.CreateArrayTypeSymbol(typeSymbol, bounds);
                                continue;
                            }
 
                            if (PeekNextChar(id, index) == '*')
                            {
                                index++;
                                typeSymbol = compilation.CreatePointerTypeSymbol(typeSymbol);
                                continue;
                            }
 
                            break;
                        }
 
                        results[i] = typeSymbol;
                        endIndex = index;
                    }
 
                    index = endIndex;
                }
            }
 
            private static void ParseTypeParameterSymbol(string id, ref int index, ISymbol? typeParameterContext, List<ISymbol> results)
            {
                // skip the first `
                System.Diagnostics.Debug.Assert(PeekNextChar(id, index) == '`');
                index++;
 
                if (PeekNextChar(id, index) == '`')
                {
                    // `` means this is a method type parameter
                    index++;
                    var methodTypeParameterIndex = ReadNextInteger(id, ref index);
 
                    var methodContext = typeParameterContext as IMethodSymbol;
                    if (methodContext != null)
                    {
                        var count = methodContext.TypeParameters.Length;
                        if (count > 0 && methodTypeParameterIndex < count)
                        {
                            results.Add(methodContext.TypeParameters[methodTypeParameterIndex]);
                        }
                    }
                }
                else
                {
                    // regular type parameter
                    var typeParameterIndex = ReadNextInteger(id, ref index);
 
                    var methodContext = typeParameterContext as IMethodSymbol;
                    var typeContext = methodContext != null ? methodContext.ContainingType : typeParameterContext as INamedTypeSymbol;
 
                    if (typeContext != null && GetNthTypeParameter(typeContext, typeParameterIndex) is { } typeParameter)
                    {
                        results.Add(typeParameter);
                    }
                }
            }
 
            private static void ParseNamedTypeSymbol(string id, ref int index, Compilation compilation, ISymbol? typeParameterContext, List<ISymbol> results)
            {
                var containers = s_namespaceOrTypeListPool.Allocate();
                try
                {
                    containers.Add(compilation.GlobalNamespace);
 
                    // loop for dotted names
                    while (true)
                    {
                        var name = ParseName(id, ref index);
 
                        List<ITypeSymbol>? typeArguments = null;
                        int arity = 0;
 
                        // type arguments
                        if (PeekNextChar(id, index) == '{')
                        {
                            typeArguments = new List<ITypeSymbol>();
                            if (!ParseTypeArguments(id, ref index, compilation, typeParameterContext, typeArguments))
                            {
                                // if no type arguments are found then the type cannot be identified
                                continue;
                            }
 
                            arity = typeArguments.Count;
                        }
                        else if (PeekNextChar(id, index) == '`')
                        {
                            index++;
                            arity = ReadNextInteger(id, ref index);
                        }
 
                        if (arity != 0 || PeekNextChar(id, index) != '.')
                        {
                            GetMatchingTypes(containers, name, arity, results);
 
                            if (arity != 0 && typeArguments != null && typeArguments.Count != 0)
                            {
                                var typeArgs = typeArguments.ToArray();
                                for (int i = 0; i < results.Count; i++)
                                {
                                    results[i] = ((INamedTypeSymbol)results[i]).Construct(typeArgs);
                                }
                            }
                        }
                        else
                        {
                            GetMatchingNamespaceOrTypes(containers, name, results);
                        }
 
                        if (PeekNextChar(id, index) == '.')
                        {
                            index++;
                            containers.Clear();
                            CopyTo(results, containers);
                            results.Clear();
                            continue;
                        }
 
                        break;
                    }
                }
                finally
                {
                    s_namespaceOrTypeListPool.ClearAndFree(containers);
                }
            }
 
            private static int ParseArrayBounds(string id, ref int index)
            {
                index++;  // skip '['
 
                int bounds = 0;
 
                while (true)
                {
                    if (char.IsDigit(PeekNextChar(id, index)))
                    {
                        ReadNextInteger(id, ref index);
                    }
 
                    if (PeekNextChar(id, index) == ':')
                    {
                        index++;
 
                        if (char.IsDigit(PeekNextChar(id, index)))
                        {
                            ReadNextInteger(id, ref index);
                        }
                    }
 
                    bounds++;
 
                    if (PeekNextChar(id, index) == ',')
                    {
                        index++;
                        continue;
                    }
 
                    break;
                }
 
                if (PeekNextChar(id, index) == ']')
                {
                    index++;
                }
 
                return bounds;
            }
 
            private static bool ParseTypeArguments(string id, ref int index, Compilation compilation, ISymbol? typeParameterContext, List<ITypeSymbol> typeArguments)
            {
                index++; // skip over {
 
                while (true)
                {
                    var type = ParseTypeSymbol(id, ref index, compilation, typeParameterContext);
 
                    if (type == null)
                    {
                        // if a type argument cannot be identified, argument list is no good
                        return false;
                    }
 
                    // add first one
                    typeArguments.Add(type);
 
                    if (PeekNextChar(id, index) == ',')
                    {
                        index++;
                        continue;
                    }
 
                    break;
                }
 
                if (PeekNextChar(id, index) == '}')
                {
                    index++;
                }
 
                return true;
            }
 
            private static void GetMatchingTypes(List<INamespaceOrTypeSymbol> containers, string memberName, int arity, List<ISymbol> results)
            {
                for (int i = 0, n = containers.Count; i < n; i++)
                {
                    GetMatchingTypes(containers[i], memberName, arity, results);
                }
            }
 
            private static void GetMatchingTypes(INamespaceOrTypeSymbol container, string memberName, int arity, List<ISymbol> results)
            {
                var members = container.GetMembers(memberName);
 
                foreach (var symbol in members)
                {
                    if (symbol.Kind == SymbolKind.NamedType)
                    {
                        var namedType = (INamedTypeSymbol)symbol;
                        if (namedType.Arity == arity)
                        {
                            results.Add(namedType);
                        }
                    }
                }
            }
 
            private static void GetMatchingNamespaceOrTypes(List<INamespaceOrTypeSymbol> containers, string memberName, List<ISymbol> results)
            {
                for (int i = 0, n = containers.Count; i < n; i++)
                {
                    GetMatchingNamespaceOrTypes(containers[i], memberName, results);
                }
            }
 
            private static void GetMatchingNamespaceOrTypes(INamespaceOrTypeSymbol container, string memberName, List<ISymbol> results)
            {
                var members = container.GetMembers(memberName);
 
                foreach (var symbol in members)
                {
                    if (symbol.Kind == SymbolKind.Namespace || (symbol.Kind == SymbolKind.NamedType && ((INamedTypeSymbol)symbol).Arity == 0))
                    {
                        results.Add(symbol);
                    }
                }
            }
 
            private static void GetMatchingNamespaces(List<INamespaceOrTypeSymbol> containers, string memberName, List<ISymbol> results)
            {
                for (int i = 0, n = containers.Count; i < n; i++)
                {
                    GetMatchingNamespaces(containers[i], memberName, results);
                }
            }
 
            private static void GetMatchingNamespaces(INamespaceOrTypeSymbol container, string memberName, List<ISymbol> results)
            {
                var members = container.GetMembers(memberName);
 
                foreach (var symbol in members)
                {
                    if (symbol.Kind == SymbolKind.Namespace)
                    {
                        results.Add(symbol);
                    }
                }
            }
 
            private static void GetMatchingMethods(string id, ref int index, List<INamespaceOrTypeSymbol> containers, string memberName, int arity, Compilation compilation, List<ISymbol> results)
            {
                var parameters = s_parameterListPool.Allocate();
                try
                {
                    var startIndex = index;
                    var endIndex = index;
 
                    for (int i = 0, n = containers.Count; i < n; i++)
                    {
                        var members = containers[i].GetMembers(memberName);
 
                        foreach (var symbol in members)
                        {
                            index = startIndex;
 
                            var methodSymbol = symbol as IMethodSymbol;
                            if (methodSymbol != null && methodSymbol.Arity == arity)
                            {
                                parameters.Clear();
 
                                if (PeekNextChar(id, index) == '(')
                                {
                                    if (!ParseParameterList(id, ref index, compilation, methodSymbol, parameters))
                                    {
                                        // if the parameters cannot be identified (some error), then the symbol cannot match, try next method symbol
                                        continue;
                                    }
                                }
 
                                if (!AllParametersMatch(methodSymbol.Parameters, parameters))
                                {
                                    // parameters don't match, try next method symbol
                                    continue;
                                }
 
                                if (PeekNextChar(id, index) == '~')
                                {
                                    index++;
                                    ITypeSymbol? returnType = ParseTypeSymbol(id, ref index, compilation, methodSymbol);
 
                                    // if return type is specified, then it must match
                                    if (returnType != null && methodSymbol.ReturnType.Equals(returnType, SymbolEqualityComparer.CLRSignature))
                                    {
                                        // return type matches
                                        results.Add(methodSymbol);
                                        endIndex = index;
                                    }
                                }
                                else
                                {
                                    // no return type specified, then any matches
                                    results.Add(methodSymbol);
                                    endIndex = index;
                                }
                            }
                        }
                    }
 
                    index = endIndex;
                }
                finally
                {
                    s_parameterListPool.ClearAndFree(parameters);
                }
            }
 
            private static void GetMatchingProperties(string id, ref int index, List<INamespaceOrTypeSymbol> containers, string memberName, Compilation compilation, List<ISymbol> results)
            {
                int startIndex = index;
                int endIndex = index;
 
                List<ParameterInfo>? parameters = null;
                try
                {
                    for (int i = 0, n = containers.Count; i < n; i++)
                    {
                        memberName = DecodePropertyName(memberName, compilation.Language);
                        var members = containers[i].GetMembers(memberName);
 
                        foreach (var symbol in members)
                        {
                            index = startIndex;
 
                            var propertySymbol = symbol as IPropertySymbol;
                            if (propertySymbol != null)
                            {
                                if (PeekNextChar(id, index) == '(')
                                {
                                    if (parameters == null)
                                    {
                                        parameters = s_parameterListPool.Allocate();
                                    }
                                    else
                                    {
                                        parameters.Clear();
                                    }
 
                                    if (ParseParameterList(id, ref index, compilation, propertySymbol.ContainingSymbol, parameters)
                                        && AllParametersMatch(propertySymbol.Parameters, parameters))
                                    {
                                        results.Add(propertySymbol);
                                        endIndex = index;
                                    }
                                }
                                else if (propertySymbol.Parameters.Length == 0)
                                {
                                    results.Add(propertySymbol);
                                    endIndex = index;
                                }
                            }
                        }
                    }
 
                    index = endIndex;
                }
                finally
                {
                    if (parameters != null)
                    {
                        s_parameterListPool.ClearAndFree(parameters);
                    }
                }
            }
 
            private static void GetMatchingFields(List<INamespaceOrTypeSymbol> containers, string memberName, List<ISymbol> results)
            {
                for (int i = 0, n = containers.Count; i < n; i++)
                {
                    var members = containers[i].GetMembers(memberName);
 
                    foreach (var symbol in members)
                    {
                        if (symbol.Kind == SymbolKind.Field)
                        {
                            results.Add(symbol);
                        }
                    }
                }
            }
 
            private static void GetMatchingEvents(List<INamespaceOrTypeSymbol> containers, string memberName, List<ISymbol> results)
            {
                for (int i = 0, n = containers.Count; i < n; i++)
                {
                    var members = containers[i].GetMembers(memberName);
 
                    foreach (var symbol in members)
                    {
                        if (symbol.Kind == SymbolKind.Event)
                        {
                            results.Add(symbol);
                        }
                    }
                }
            }
 
            private static bool AllParametersMatch(ImmutableArray<IParameterSymbol> symbolParameters, List<ParameterInfo> expectedParameters)
            {
                if (symbolParameters.Length != expectedParameters.Count)
                {
                    return false;
                }
 
                for (int i = 0; i < expectedParameters.Count; i++)
                {
                    if (!ParameterMatches(symbolParameters[i], expectedParameters[i]))
                    {
                        return false;
                    }
                }
 
                return true;
            }
 
            private static bool ParameterMatches(IParameterSymbol symbol, ParameterInfo parameterInfo)
            {
                // same ref'ness?
                if ((symbol.RefKind == RefKind.None) == parameterInfo.IsRefOrOut)
                {
                    return false;
                }
 
                var parameterType = parameterInfo.Type;
 
                return parameterType != null && symbol.Type.Equals(parameterType, SymbolEqualityComparer.CLRSignature);
            }
 
            private static ITypeParameterSymbol? GetNthTypeParameter(INamedTypeSymbol typeSymbol, int n)
            {
                var containingTypeParameterCount = GetTypeParameterCount(typeSymbol.ContainingType);
                if (n < containingTypeParameterCount)
                {
                    return GetNthTypeParameter(typeSymbol.ContainingType, n);
                }
 
                var index = n - containingTypeParameterCount;
                var typeParameters = typeSymbol.TypeParameters;
                if (index < typeParameters.Length)
                {
                    return typeParameters[index];
                }
 
                return null;
            }
 
            private static int GetTypeParameterCount(INamedTypeSymbol typeSymbol)
            {
                if (typeSymbol == null)
                {
                    return 0;
                }
 
                return typeSymbol.TypeParameters.Length + GetTypeParameterCount(typeSymbol.ContainingType);
            }
 
            [StructLayout(LayoutKind.Auto)]
            private readonly struct ParameterInfo
            {
                internal readonly ITypeSymbol Type;
                internal readonly bool IsRefOrOut;
 
                public ParameterInfo(ITypeSymbol type, bool isRefOrOut)
                {
                    this.Type = type;
                    this.IsRefOrOut = isRefOrOut;
                }
            }
 
            private static readonly ListPool<ParameterInfo> s_parameterListPool = new ListPool<ParameterInfo>();
 
            private static bool ParseParameterList(string id, ref int index, Compilation compilation, ISymbol typeParameterContext, List<ParameterInfo> parameters)
            {
                System.Diagnostics.Debug.Assert(typeParameterContext != null);
 
                index++; // skip over '('
 
                if (PeekNextChar(id, index) == ')')
                {
                    index++;
                    return true;
                }
 
                var parameter = ParseParameter(id, ref index, compilation, typeParameterContext);
                if (parameter == null)
                {
                    return false;
                }
 
                parameters.Add(parameter.Value);
 
                while (PeekNextChar(id, index) == ',')
                {
                    index++;
 
                    parameter = ParseParameter(id, ref index, compilation, typeParameterContext);
                    if (parameter == null)
                    {
                        return false;
                    }
 
                    parameters.Add(parameter.Value);
                }
 
                if (PeekNextChar(id, index) == ')')
                {
                    index++;
                }
 
                return true;
            }
 
            private static ParameterInfo? ParseParameter(string id, ref int index, Compilation compilation, ISymbol? typeParameterContext)
            {
                bool isRefOrOut = false;
 
                var type = ParseTypeSymbol(id, ref index, compilation, typeParameterContext);
 
                if (type == null)
                {
                    // if no type can be identified, then there is no parameter
                    return null;
                }
 
                if (PeekNextChar(id, index) == '@')
                {
                    index++;
                    isRefOrOut = true;
                }
 
                return new ParameterInfo(type, isRefOrOut);
            }
 
            private static char PeekNextChar(string id, int index)
            {
                return index >= id.Length ? '\0' : id[index];
            }
 
            private static readonly char[] s_nameDelimiters = { ':', '.', '(', ')', '{', '}', '[', ']', ',', '\'', '@', '*', '`', '~' };
 
            private static string ParseName(string id, ref int index)
            {
                string name;
 
                int delimiterOffset = id.IndexOfAny(s_nameDelimiters, index);
                if (delimiterOffset >= 0)
                {
                    name = id.Substring(index, delimiterOffset - index);
                    index = delimiterOffset;
                }
                else
                {
                    name = id.Substring(index);
                    index = id.Length;
                }
 
                return DecodeName(name);
            }
 
            // undoes dot encodings within names...
            private static string DecodeName(string name)
            {
                if (name.IndexOf('#') >= 0)
                {
                    return name.Replace('#', '.');
                }
 
                return name;
            }
 
            private static int ReadNextInteger(string id, ref int index)
            {
                int n = 0;
 
                while (index < id.Length && char.IsDigit(id[index]))
                {
                    n = n * 10 + (id[index] - '0');
                    index++;
                }
 
                return n;
            }
 
            private static void CopyTo<TSource, TDestination>(List<TSource> source, List<TDestination> destination)
                where TSource : class
                where TDestination : class
            {
                if (destination.Count + source.Count > destination.Capacity)
                {
                    destination.Capacity = destination.Count + source.Count;
                }
 
                for (int i = 0, n = source.Count; i < n; i++)
                {
                    destination.Add((TDestination)(object)source[i]);
                }
            }
        }
    }
}