File: MetadataReader\MetadataHelpers.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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal static class MetadataHelpers
    {
        // https://github.com/dotnet/roslyn/issues/73548:
        // Remove this constant and refer to GenericParameterAttributes.AllowByRefLike directly once the new enum member becomes available.
        // See // https://github.com/dotnet/runtime/issues/68002#issuecomment-1942166436 for more details.
        public const System.Reflection.GenericParameterAttributes GenericParameterAttributesAllowByRefLike = (System.Reflection.GenericParameterAttributes)0x0020;
 
        public const char DotDelimiter = '.';
        public const string DotDelimiterString = ".";
        public const char GenericTypeNameManglingChar = '`';
        private const string GenericTypeNameManglingString = "`";
        public const int MaxStringLengthForParamSize = 22;
        public const int MaxStringLengthForIntToStringConversion = 22;
        public const string SystemString = "System";
 
        // These can appear in the interface name that precedes an explicit interface implementation member.
        public const char MangledNameRegionStartChar = '<';
        public const char MangledNameRegionEndChar = '>';
 
        internal readonly struct AssemblyQualifiedTypeName
        {
            internal readonly string TopLevelType;
            internal readonly string[] NestedTypes;
            internal readonly AssemblyQualifiedTypeName[] TypeArguments;
            internal readonly int PointerCount;
 
            /// <summary>
            /// Rank equal 0 is used to denote an SzArray, rank equal 1 denotes multi-dimensional array of rank 1.
            /// </summary>
            internal readonly int[] ArrayRanks;
            internal readonly string AssemblyName;
 
            internal AssemblyQualifiedTypeName(
                string topLevelType,
                string[] nestedTypes,
                AssemblyQualifiedTypeName[] typeArguments,
                int pointerCount,
                int[] arrayRanks,
                string assemblyName)
            {
                this.TopLevelType = topLevelType;
                this.NestedTypes = nestedTypes;
                this.TypeArguments = typeArguments;
                this.PointerCount = pointerCount;
                this.ArrayRanks = arrayRanks;
                this.AssemblyName = assemblyName;
            }
        }
 
        internal static AssemblyQualifiedTypeName DecodeTypeName(string s)
        {
            var decoder = new SerializedTypeDecoder(s);
            return decoder.DecodeTypeName();
        }
 
        /// <summary>
        /// Decodes a serialized type name in its canonical form. The canonical name is its full type name, followed
        /// optionally by the assembly where it is defined, its version, culture and public key token.  If the assembly
        /// name is omitted, the type name is in the current assembly otherwise it is in the referenced assembly. The
        /// full type name is the fully qualified metadata type name. 
        /// </summary>
        private struct SerializedTypeDecoder
        {
            private static readonly char[] s_typeNameDelimiters = { '+', ',', '[', ']', '*' };
            private readonly string _input;
            private int _offset;
 
            internal SerializedTypeDecoder(string s)
            {
                _input = s;
                _offset = 0;
            }
 
            private void Advance()
            {
                if (!EndOfInput)
                {
                    _offset++;
                }
            }
 
            private void AdvanceTo(int i)
            {
                if (i <= _input.Length)
                {
                    _offset = i;
                }
            }
 
            private bool EndOfInput
            {
                get
                {
                    return _offset >= _input.Length;
                }
            }
 
            private char Current
            {
                get
                {
                    return _input[_offset];
                }
            }
 
            /// <summary>
            /// Decodes a type name.  A type name is a string which is terminated by the end of the string or one of the
            /// delimiters '+', ',', '[', ']'. '+' separates nested classes. '[' and ']'
            /// enclosed generic type arguments.  ',' separates types.
            /// </summary>
            internal AssemblyQualifiedTypeName DecodeTypeName(bool isTypeArgument = false, bool isTypeArgumentWithAssemblyName = false)
            {
                Debug.Assert(!isTypeArgumentWithAssemblyName || isTypeArgument);
 
                string topLevelType = null;
                ArrayBuilder<string> nestedTypesBuilder = null;
                AssemblyQualifiedTypeName[] typeArguments = null;
                int pointerCount = 0;
                ArrayBuilder<int> arrayRanksBuilder = null;
                string assemblyName = null;
                bool decodingTopLevelType = true;
                bool isGenericTypeName = false;
 
                var pooledStrBuilder = PooledStringBuilder.GetInstance();
                StringBuilder typeNameBuilder = pooledStrBuilder.Builder;
 
                while (!EndOfInput)
                {
                    int i = _input.IndexOfAny(s_typeNameDelimiters, _offset);
                    if (i >= 0)
                    {
                        char c = _input[i];
 
                        // Found name, which could be a generic name with arity.
                        // Generic type parameter count, if any, are handled in DecodeGenericName.
                        string decodedString = DecodeGenericName(i);
                        Debug.Assert(decodedString != null);
 
                        // Type name is generic if the decoded name of the top level type OR any of the outer types of a nested type had the '`' character.
                        isGenericTypeName = isGenericTypeName || decodedString.IndexOf(GenericTypeNameManglingChar) >= 0;
                        typeNameBuilder.Append(decodedString);
 
                        switch (c)
                        {
                            case '*':
                                if (arrayRanksBuilder != null)
                                {
                                    // Error case, array shape must be specified at the end of the type name.
                                    // Process as a regular character and continue.
                                    typeNameBuilder.Append(c);
                                }
                                else
                                {
                                    pointerCount++;
                                }
 
                                Advance();
                                break;
 
                            case '+':
                                if (arrayRanksBuilder != null || pointerCount > 0)
                                {
                                    // Error case, array shape must be specified at the end of the type name.
                                    // Process as a regular character and continue.
                                    typeNameBuilder.Append(c);
                                }
                                else
                                {
                                    // Type followed by nested type. Handle nested class separator and collect the nested types.
                                    HandleDecodedTypeName(typeNameBuilder.ToString(), decodingTopLevelType, ref topLevelType, ref nestedTypesBuilder);
                                    typeNameBuilder.Clear();
                                    decodingTopLevelType = false;
                                }
 
                                Advance();
                                break;
 
                            case '[':
                                // Is type followed by generic type arguments?
                                if (isGenericTypeName && typeArguments == null)
                                {
                                    Advance();
                                    if (arrayRanksBuilder != null || pointerCount > 0)
                                    {
                                        // Error case, array shape must be specified at the end of the type name.
                                        // Process as a regular character and continue.
                                        typeNameBuilder.Append(c);
                                    }
                                    else
                                    {
                                        // Decode type arguments.
                                        typeArguments = DecodeTypeArguments();
                                    }
                                }
                                else
                                {
                                    // Decode array shape.
                                    DecodeArrayShape(typeNameBuilder, ref arrayRanksBuilder);
                                }
 
                                break;
 
                            case ']':
                                if (isTypeArgument)
                                {
                                    // End of type arguments.  This occurs when the last type argument is a type in the
                                    // current assembly.
                                    goto ExitDecodeTypeName;
                                }
                                else
                                {
                                    // Error case, process as a regular character and continue.
                                    typeNameBuilder.Append(c);
                                    Advance();
                                    break;
                                }
 
                            case ',':
                                // A comma may separate a type name from its assembly name or a type argument from
                                // another type argument.
                                // If processing non-type argument or a type argument with assembly name,
                                // process the characters after the comma as an assembly name.
                                if (!isTypeArgument || isTypeArgumentWithAssemblyName)
                                {
                                    Advance();
                                    if (!EndOfInput && Char.IsWhiteSpace(Current))
                                    {
                                        Advance();
                                    }
 
                                    assemblyName = DecodeAssemblyName(isTypeArgumentWithAssemblyName);
                                }
                                goto ExitDecodeTypeName;
 
                            default:
                                throw ExceptionUtilities.UnexpectedValue(c);
                        }
                    }
                    else
                    {
                        typeNameBuilder.Append(DecodeGenericName(_input.Length));
                        goto ExitDecodeTypeName;
                    }
                }
 
ExitDecodeTypeName:
                HandleDecodedTypeName(typeNameBuilder.ToString(), decodingTopLevelType, ref topLevelType, ref nestedTypesBuilder);
                pooledStrBuilder.Free();
 
                return new AssemblyQualifiedTypeName(
                    topLevelType,
                    nestedTypesBuilder?.ToArrayAndFree(),
                    typeArguments,
                    pointerCount,
                    arrayRanksBuilder?.ToArrayAndFree(),
                    assemblyName);
            }
 
            private static void HandleDecodedTypeName(string decodedTypeName, bool decodingTopLevelType, ref string topLevelType, ref ArrayBuilder<string> nestedTypesBuilder)
            {
                if (decodedTypeName.Length != 0)
                {
                    if (decodingTopLevelType)
                    {
                        Debug.Assert(topLevelType == null);
                        topLevelType = decodedTypeName;
                    }
                    else
                    {
                        if (nestedTypesBuilder == null)
                        {
                            nestedTypesBuilder = ArrayBuilder<string>.GetInstance();
                        }
 
                        nestedTypesBuilder.Add(decodedTypeName);
                    }
                }
            }
 
            /// <summary>
            /// Decodes a generic name.  This is a type name followed optionally by a type parameter count
            /// </summary>
            private string DecodeGenericName(int i)
            {
                Debug.Assert(i == _input.Length || s_typeNameDelimiters.Contains(_input[i]));
 
                var length = i - _offset;
                if (length == 0)
                {
                    return String.Empty;
                }
 
                // Save start of name. The name should be the emitted name including the '`'  and arity.
                int start = _offset;
                AdvanceTo(i);
 
                // Get the emitted name.
                return _input.Substring(start, _offset - start);
            }
 
            private AssemblyQualifiedTypeName[] DecodeTypeArguments()
            {
                if (EndOfInput)
                {
                    return null;
                }
 
                var typeBuilder = ArrayBuilder<AssemblyQualifiedTypeName>.GetInstance();
 
                while (!EndOfInput)
                {
                    typeBuilder.Add(DecodeTypeArgument());
 
                    if (!EndOfInput)
                    {
                        switch (Current)
                        {
                            case ',':
                                // More type arguments follow
                                Advance();
                                if (!EndOfInput && Char.IsWhiteSpace(Current))
                                {
                                    Advance();
                                }
                                break;
 
                            case ']':
                                // End of type arguments
                                Advance();
                                return typeBuilder.ToArrayAndFree();
 
                            default:
                                throw ExceptionUtilities.UnexpectedValue(EndOfInput);
                        }
                    }
                }
 
                return typeBuilder.ToArrayAndFree();
            }
 
            private AssemblyQualifiedTypeName DecodeTypeArgument()
            {
                bool isTypeArgumentWithAssemblyName = false;
                if (Current == '[')
                {
                    isTypeArgumentWithAssemblyName = true;
                    Advance();
                }
 
                AssemblyQualifiedTypeName result = DecodeTypeName(isTypeArgument: true, isTypeArgumentWithAssemblyName: isTypeArgumentWithAssemblyName);
 
                if (isTypeArgumentWithAssemblyName)
                {
                    if (!EndOfInput && Current == ']')
                    {
                        Advance();
                    }
                }
 
                return result;
            }
 
            private string DecodeAssemblyName(bool isTypeArgumentWithAssemblyName)
            {
                if (EndOfInput)
                {
                    return null;
                }
 
                int i;
                if (isTypeArgumentWithAssemblyName)
                {
                    i = _input.IndexOf(']', _offset);
                    if (i < 0)
                    {
                        i = _input.Length;
                    }
                }
                else
                {
                    i = _input.Length;
                }
 
                string name = _input.Substring(_offset, i - _offset);
                AdvanceTo(i);
                return name;
            }
 
            /// <summary>
            /// Rank equal 0 is used to denote an SzArray, rank equal 1 denotes multi-dimensional array of rank 1.
            /// </summary>
            private void DecodeArrayShape(StringBuilder typeNameBuilder, ref ArrayBuilder<int> arrayRanksBuilder)
            {
                Debug.Assert(Current == '[');
 
                int start = _offset;
                int rank = 1;
                bool isMultiDimensionalIfRankOne = false;
                Advance();
 
                while (!EndOfInput)
                {
                    switch (Current)
                    {
                        case ',':
                            rank++;
                            Advance();
                            break;
 
                        case ']':
                            if (arrayRanksBuilder == null)
                            {
                                arrayRanksBuilder = ArrayBuilder<int>.GetInstance();
                            }
 
                            arrayRanksBuilder.Add(rank == 1 && !isMultiDimensionalIfRankOne ? 0 : rank);
                            Advance();
                            return;
 
                        case '*':
                            if (rank != 1)
                            {
                                goto default;
                            }
 
                            Advance();
                            if (Current != ']')
                            {
                                // Error case, process as regular characters
                                typeNameBuilder.Append(_input.Substring(start, _offset - start));
                                return;
                            }
 
                            isMultiDimensionalIfRankOne = true;
                            break;
 
                        default:
                            // Error case, process as regular characters
                            Advance();
                            typeNameBuilder.Append(_input.Substring(start, _offset - start));
                            return;
                    }
                }
 
                // Error case, process as regular characters
                typeNameBuilder.Append(_input.Substring(start, _offset - start));
            }
        }
 
        private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" };
 
        internal static string GetAritySuffix(int arity)
        {
            Debug.Assert(arity > 0);
            return (arity <= 9) ? s_aritySuffixesOneToNine[arity - 1] : string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture));
        }
 
#nullable enable
        internal static string ComposeAritySuffixedMetadataName(string name, int arity, string? associatedFileIdentifier)
        {
            return associatedFileIdentifier + (arity == 0 ? name : name + GetAritySuffix(arity));
        }
#nullable disable
 
        internal static int InferTypeArityFromMetadataName(string emittedTypeName)
        {
            int suffixStartsAt;
            return InferTypeArityFromMetadataName(emittedTypeName.AsSpan(), out suffixStartsAt);
        }
 
        private static short InferTypeArityFromMetadataName(ReadOnlySpan<char> emittedTypeName, out int suffixStartsAt)
        {
            Debug.Assert(emittedTypeName != null, "NULL actual name unexpected!!!");
            int emittedTypeNameLength = emittedTypeName.Length;
 
            int indexOfManglingChar;
            for (indexOfManglingChar = emittedTypeNameLength; indexOfManglingChar >= 1; indexOfManglingChar--)
            {
                if (emittedTypeName[indexOfManglingChar - 1] == GenericTypeNameManglingChar)
                {
                    break;
                }
            }
 
            if (indexOfManglingChar < 2 ||
               (emittedTypeNameLength - indexOfManglingChar) == 0 ||
               emittedTypeNameLength - indexOfManglingChar > MaxStringLengthForParamSize)
            {
                suffixStartsAt = -1;
                return 0;
            }
 
            // Given a name corresponding to <unmangledName>`<arity>, extract the arity.
            if (tryScanArity(emittedTypeName[indexOfManglingChar..]) is not short arity)
            {
                suffixStartsAt = -1;
                return 0;
            }
 
            suffixStartsAt = indexOfManglingChar - 1;
            return arity;
 
            static short? tryScanArity(ReadOnlySpan<char> aritySpan)
            {
                // Arity must have at least one character and must not have leading zeroes.
                // Also, in order to fit into short.MaxValue (32767), it must be at most 5 characters long. 
                if (aritySpan is { Length: >= 1 and <= 5 } and not ['0', ..])
                {
                    int intArity = 0;
                    foreach (char digit in aritySpan)
                    {
                        // Accepting integral decimal digits only
                        if (digit is < '0' or > '9')
                            return null;
 
                        intArity = intArity * 10 + (digit - '0');
                    }
 
                    Debug.Assert(intArity > 0);
 
                    if (intArity <= short.MaxValue)
                        return (short)intArity;
                }
 
                return null;
            }
        }
 
        internal static string InferTypeArityAndUnmangleMetadataName(string emittedTypeName, out short arity)
        {
            var emittedTypeNameMemory = emittedTypeName.AsMemory();
            var resultMemory = InferTypeArityAndUnmangleMetadataName(emittedTypeNameMemory, out arity);
            var resultString = resultMemory.ToString();
 
            Debug.Assert(!resultMemory.Equals(emittedTypeNameMemory) || ReferenceEquals(resultString, emittedTypeName), "If the name was not mangled, we should get the original string instance back.");
            return resultString;
        }
 
        internal static ReadOnlyMemory<char> InferTypeArityAndUnmangleMetadataName(ReadOnlyMemory<char> emittedTypeName, out short arity)
        {
            int suffixStartsAt;
            arity = InferTypeArityFromMetadataName(emittedTypeName.Span, out suffixStartsAt);
 
            if (arity == 0)
            {
                Debug.Assert(suffixStartsAt == -1);
                return emittedTypeName;
            }
 
            Debug.Assert(suffixStartsAt > 0 && suffixStartsAt < emittedTypeName.Length - 1);
            return emittedTypeName[..suffixStartsAt];
        }
 
        internal static string UnmangleMetadataNameForArity(string emittedTypeName, int arity)
        {
            Debug.Assert(arity > 0);
 
            int suffixStartsAt;
            if (arity == InferTypeArityFromMetadataName(emittedTypeName.AsSpan(), out suffixStartsAt))
            {
                Debug.Assert(suffixStartsAt > 0 && suffixStartsAt < emittedTypeName.Length - 1);
                return emittedTypeName[..suffixStartsAt];
            }
 
            return emittedTypeName;
        }
 
        /// <summary>
        /// An ImmutableArray representing the single string "System"
        /// </summary>
        private static readonly ImmutableArray<string> s_splitQualifiedNameSystem = ImmutableArray.Create(SystemString);
        private static readonly ImmutableArray<ReadOnlyMemory<char>> s_splitQualifiedNameSystemMemory = ImmutableArray.Create(SystemString.AsMemory());
 
        internal static ImmutableArray<string> SplitQualifiedName(string name)
            => SplitQualifiedNameWorker(name.AsMemory(), s_splitQualifiedNameSystem, static memory => memory.ToString());
 
        internal static ImmutableArray<ReadOnlyMemory<char>> SplitQualifiedName(ReadOnlyMemory<char> name)
            => SplitQualifiedNameWorker(name, s_splitQualifiedNameSystemMemory, static memory => memory);
 
        internal static ImmutableArray<T> SplitQualifiedNameWorker<T>(
            ReadOnlyMemory<char> nameMemory, ImmutableArray<T> splitSystemString, Func<ReadOnlyMemory<char>, T> convert)
        {
            Debug.Assert(!nameMemory.Equals(default(ReadOnlyMemory<char>)));
 
            if (nameMemory.Length == 0)
            {
                return ImmutableArray<T>.Empty;
            }
 
            // PERF: Avoid String.Split because of the allocations. Also, we can special-case
            // for "System" if it is the first or only part.
 
            int dots = 0;
            var nameSpan = nameMemory.Span;
            foreach (char ch in nameSpan)
            {
                if (ch == DotDelimiter)
                {
                    dots++;
                }
            }
 
            if (dots == 0)
            {
                return nameMemory.Span.SequenceEqual(SystemString.AsSpan()) ? splitSystemString : ImmutableArray.Create(convert(nameMemory));
            }
 
            var result = ArrayBuilder<T>.GetInstance(dots + 1);
 
            int start = 0;
            for (int i = 0; dots > 0; i++)
            {
                if (nameSpan[i] == DotDelimiter)
                {
                    int len = i - start;
                    if (len == 6 && start == 0 && nameSpan.StartsWith(SystemString.AsSpan(), StringComparison.Ordinal))
                    {
                        result.Add(convert(SystemString.AsMemory()));
                    }
                    else
                    {
                        result.Add(convert(nameMemory.Slice(start, len)));
                    }
 
                    dots--;
                    start = i + 1;
                }
            }
 
            result.Add(convert(nameMemory[start..]));
 
            return result.ToImmutableAndFree();
        }
 
        internal static string SplitQualifiedName(
            string pstrName,
            out string qualifier)
        {
            ReadOnlyMemory<char> nameMemory = SplitQualifiedName(pstrName, out ReadOnlyMemory<char> qualifierMemory);
            qualifier = qualifierMemory.ToString();
            return nameMemory.ToString();
        }
 
        internal static ReadOnlyMemory<char> SplitQualifiedName(
            string pstrName,
            out ReadOnlyMemory<char> qualifier)
        {
            Debug.Assert(pstrName != null);
 
            // In mangled names, the original unmangled name is frequently included,
            // surrounded by angle brackets.  The unmangled name may contain dots
            // (e.g. if it is an explicit interface implementation) or paired angle
            // brackets (e.g. if the explicitly implemented interface is generic).
            var angleBracketDepth = 0;
            var delimiter = -1;
            for (int i = 0; i < pstrName.Length; i++)
            {
                switch (pstrName[i])
                {
                    case MangledNameRegionStartChar:
                        angleBracketDepth++;
                        break;
                    case MangledNameRegionEndChar:
                        angleBracketDepth--;
                        break;
                    case DotDelimiter:
                        // If we see consecutive dots, the second is part of the method name
                        // (i.e. ".ctor" or ".cctor").
                        if (angleBracketDepth == 0 && (i == 0 || delimiter < i - 1))
                        {
                            delimiter = i;
                        }
                        break;
                }
            }
            Debug.Assert(angleBracketDepth == 0);
 
            if (delimiter < 0)
            {
                qualifier = string.Empty.AsMemory();
                return pstrName.AsMemory();
            }
 
            if (delimiter == 6 && pstrName.StartsWith(SystemString, StringComparison.Ordinal))
            {
                qualifier = SystemString.AsMemory();
            }
            else
            {
                qualifier = pstrName.AsMemory()[..delimiter];
            }
 
            return pstrName.AsMemory()[(delimiter + 1)..];
        }
 
        internal static string BuildQualifiedName(
            string qualifier,
            string name)
        {
            Debug.Assert(name != null);
 
            if (!string.IsNullOrEmpty(qualifier))
            {
                return String.Concat(qualifier, DotDelimiterString, name);
            }
 
            return name;
        }
 
        /// <summary>
        /// Calculates information about types and namespaces immediately contained within a namespace.
        /// </summary>
        /// <param name="isGlobalNamespace">
        /// Is current namespace a global namespace?
        /// </param>
        /// <param name="namespaceNameLength">
        /// Length of the fully-qualified name of this namespace.
        /// </param>
        /// <param name="typesByNS">
        /// The sequence of groups of TypeDef row ids for types contained within the namespace, 
        /// recursively including those from nested namespaces. The row ids must be grouped by the 
        /// fully-qualified namespace name in case-sensitive manner. 
        /// Key of each IGrouping is a fully-qualified namespace name, which starts with the name of 
        /// this namespace. There could be multiple groups for each fully-qualified namespace name.
        /// 
        /// The groups must be sorted by the keys in a manner consistent with comparer passed in as
        /// nameComparer. Therefore, all types immediately contained within THIS namespace, if any, 
        /// must be in several IGrouping at the very beginning of the sequence.
        /// </param>
        /// <param name="nameComparer">
        /// Equality comparer to compare namespace names.
        /// </param>
        /// <param name="types">
        /// Output parameter, never null:
        /// A sequence of groups of TypeDef row ids for types immediately contained within this namespace.
        /// </param>
        /// <param name="namespaces">
        /// Output parameter, never null:
        /// A sequence with information about namespaces immediately contained within this namespace.
        /// For each pair:
        ///   Key - contains simple name of a child namespace.
        ///   Value - contains a sequence similar to the one passed to this function, but
        ///           calculated for the child namespace. 
        /// </param>
        /// <remarks></remarks>
        public static void GetInfoForImmediateNamespaceMembers(
            bool isGlobalNamespace,
            int namespaceNameLength,
            IEnumerable<IGrouping<string, TypeDefinitionHandle>> typesByNS,
            StringComparer nameComparer,
            out IEnumerable<IGrouping<string, TypeDefinitionHandle>> types,
            out IEnumerable<KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>> namespaces)
        {
            Debug.Assert(typesByNS != null);
            Debug.Assert(namespaceNameLength >= 0);
            Debug.Assert(!isGlobalNamespace || namespaceNameLength == 0);
 
            // A list of groups of TypeDef row ids for types immediately contained within this namespace.
            var nestedTypes = new List<IGrouping<string, TypeDefinitionHandle>>();
 
            // A list accumulating information about namespaces immediately contained within this namespace.
            // For each pair:
            //   Key - contains simple name of a child namespace.
            //   Value – contains a sequence similar to the one passed to this function, but
            //           calculated for the child namespace. 
            var nestedNamespaces = new List<KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>>();
            bool possiblyHavePairsWithDuplicateKey = false;
 
            var enumerator = typesByNS.GetEnumerator();
 
            using (enumerator)
            {
                if (enumerator.MoveNext())
                {
                    var pair = enumerator.Current;
 
                    // Simple name of the last encountered child namespace.
                    string lastChildNamespaceName = null;
 
                    // A list accumulating information about types within the last encountered child namespace.
                    // The list is similar to the sequence passed to this function.
                    List<IGrouping<string, TypeDefinitionHandle>> typesInLastChildNamespace = null;
 
                    // if there are any types in this namespace,
                    // they will be in the first several groups if their key length 
                    // is equal to namespaceNameLength.
                    while (pair.Key.Length == namespaceNameLength)
                    {
                        nestedTypes.Add(pair);
 
                        if (!enumerator.MoveNext())
                        {
                            goto DoneWithSequence;
                        }
 
                        pair = enumerator.Current;
                    }
 
                    // Account for the dot following THIS namespace name.
                    if (!isGlobalNamespace)
                    {
                        namespaceNameLength++;
                    }
 
                    do
                    {
                        pair = enumerator.Current;
 
                        string childNamespaceName = ExtractSimpleNameOfChildNamespace(namespaceNameLength, pair.Key);
 
                        int cmp = nameComparer.Compare(lastChildNamespaceName, childNamespaceName);
                        if (cmp == 0)
                        {
                            // We are still processing the same child namespace
                            typesInLastChildNamespace.Add(pair);
                        }
                        else
                        {
                            // This is a new child namespace
                            if (cmp > 0)
                            {
                                // The sort order is violated for child namespace names. Obfuscation is the likely reason for this. 
                                Debug.Assert((object)lastChildNamespaceName != null);
                                possiblyHavePairsWithDuplicateKey = true;
                            }
 
                            // Preserve information about previous child namespace.
                            if (typesInLastChildNamespace != null)
                            {
                                Debug.Assert(typesInLastChildNamespace.Count != 0);
                                nestedNamespaces.Add(
                                    new KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>(
                                        lastChildNamespaceName, typesInLastChildNamespace));
                            }
 
                            typesInLastChildNamespace = new List<IGrouping<string, TypeDefinitionHandle>>();
                            lastChildNamespaceName = childNamespaceName;
                            Debug.Assert((object)lastChildNamespaceName != null);
 
                            typesInLastChildNamespace.Add(pair);
                        }
                    }
                    while (enumerator.MoveNext());
 
                    // Preserve information about last child namespace.
                    if (typesInLastChildNamespace != null)
                    {
                        Debug.Assert(typesInLastChildNamespace.Count != 0);
                        nestedNamespaces.Add(
                            new KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>(
                                lastChildNamespaceName, typesInLastChildNamespace));
                    }
 
DoneWithSequence:
/*empty statement*/
                    ;
                }
            } // using
 
            types = nestedTypes;
 
            // Merge pairs with the same key
            if (possiblyHavePairsWithDuplicateKey)
            {
                var names = new Dictionary<string, int>(nestedNamespaces.Count, nameComparer);
 
                for (int i = nestedNamespaces.Count - 1; i >= 0; i--)
                {
                    names[nestedNamespaces[i].Key] = i;
                }
 
                if (names.Count != nestedNamespaces.Count) // nothing to merge otherwise
                {
                    for (int i = 1; i < nestedNamespaces.Count; i++)
                    {
                        var pair = nestedNamespaces[i];
                        int keyIndex = names[pair.Key];
                        if (keyIndex != i)
                        {
                            Debug.Assert(keyIndex < i);
                            var primaryPair = nestedNamespaces[keyIndex];
                            nestedNamespaces[keyIndex] = KeyValuePairUtil.Create(primaryPair.Key, primaryPair.Value.Concat(pair.Value));
                            nestedNamespaces[i] = default(KeyValuePair<string, IEnumerable<IGrouping<string, TypeDefinitionHandle>>>);
                        }
                    }
 
                    int removed = nestedNamespaces.RemoveAll(pair => (object)pair.Key == null);
                    Debug.Assert(removed > 0);
                }
            }
 
            namespaces = nestedNamespaces;
 
            Debug.Assert(types != null);
            Debug.Assert(namespaces != null);
        }
 
        /// <summary>
        /// Extract a simple name of a top level child namespace from potentially qualified namespace name.
        /// </summary>
        /// <param name="parentNamespaceNameLength">
        /// Parent namespace name length plus the dot.
        /// </param>
        /// <param name="fullName">
        /// Fully qualified namespace name.
        /// </param>
        /// <returns>
        /// Simple name of a top level child namespace, the left-most name following parent namespace name 
        /// in the fully qualified name.
        /// </returns>
        private static string ExtractSimpleNameOfChildNamespace(
            int parentNamespaceNameLength,
            string fullName)
        {
            int index = fullName.IndexOf('.', parentNamespaceNameLength);
 
            if (index < 0)
            {
                return fullName.Substring(parentNamespaceNameLength);
            }
            else
            {
                return fullName.Substring(parentNamespaceNameLength, index - parentNamespaceNameLength);
            }
        }
 
        /// <summary>
        /// Determines whether given string can be used as a non-empty metadata identifier (a NUL-terminated UTF-8 string).
        /// </summary>
        internal static bool IsValidMetadataIdentifier(string str)
        {
            return !string.IsNullOrEmpty(str) && str.IsValidUnicodeString() && str.IndexOf('\0') == -1;
        }
 
        /// <summary>
        /// True if the string doesn't contain incomplete surrogates.
        /// </summary>
        internal static bool IsValidUnicodeString(string str)
        {
            return str == null || str.IsValidUnicodeString();
        }
 
        internal static bool IsValidAssemblyOrModuleName(string name)
        {
            return GetAssemblyOrModuleNameErrorArgumentResourceName(name) == null;
        }
 
        internal static void CheckAssemblyOrModuleName(string name, CommonMessageProvider messageProvider, int code, DiagnosticBag diagnostics)
        {
            string errorArgumentResourceId = GetAssemblyOrModuleNameErrorArgumentResourceName(name);
            if (errorArgumentResourceId != null)
            {
                diagnostics.Add(
                    messageProvider.CreateDiagnostic(code, Location.None,
                        new CodeAnalysisResourcesLocalizableErrorArgument(errorArgumentResourceId)));
            }
        }
 
        internal static void CheckAssemblyOrModuleName(string name, CommonMessageProvider messageProvider, int code, ArrayBuilder<Diagnostic> builder)
        {
            string errorArgumentResourceId = GetAssemblyOrModuleNameErrorArgumentResourceName(name);
            if (errorArgumentResourceId != null)
            {
                builder.Add(
                    messageProvider.CreateDiagnostic(code, Location.None,
                        new CodeAnalysisResourcesLocalizableErrorArgument(errorArgumentResourceId)));
            }
        }
 
        private static string GetAssemblyOrModuleNameErrorArgumentResourceName(string name)
        {
            if (name == null)
            {
                return nameof(CodeAnalysisResources.NameCannotBeNull);
            }
 
            // Dev11 VB can produce assembly with no name (vbc /out:".dll" /target:library). 
            // We disallow it. PEVerify reports an error: Assembly has no name.
            if (name.Length == 0)
            {
                return nameof(CodeAnalysisResources.NameCannotBeEmpty);
            }
 
            // Dev11 VB can produce assembly that starts with whitespace (vbc /out:" a.dll" /target:library). 
            // We disallow it. PEVerify reports an error: Assembly name contains leading spaces.
            if (char.IsWhiteSpace(name[0]))
            {
                return nameof(CodeAnalysisResources.NameCannotStartWithWhitespace);
            }
 
            if (!IsValidMetadataFileName(name))
            {
                return nameof(CodeAnalysisResources.NameContainsInvalidCharacter);
            }
 
            return null;
        }
 
        /// <summary>
        /// Checks that the specified name is a valid metadata String and a file name.
        /// The specification isn't entirely consistent and complete but it mentions:
        /// 
        /// 22.19.2: "Name shall index a non-empty string in the String heap. It shall be in the format {filename}.{extension} (e.g., 'goo.dll', but not 'c:\utils\goo.dll')."
        /// 22.30.2: "The format of Name is {file name}.{file extension} with no path or drive letter; on POSIX-compliant systems Name contains no colon, no forward-slash, no backslash."
        ///          As Microsoft specific constraint.
        /// 
        /// A reasonable restriction seems to be a valid UTF-8 non-empty string that doesn't contain '\0', '\', '/', ':' characters.
        /// </summary>
        internal static bool IsValidMetadataFileName(string name)
        {
            return FileNameUtilities.IsFileName(name) && IsValidMetadataIdentifier(name);
        }
 
        /// <summary>
        /// Determine if the given namespace and type names combine to produce the given fully qualified name.
        /// </summary>
        /// <param name="namespaceName">The namespace part of the split name.</param>
        /// <param name="typeName">The type name part of the split name.</param>
        /// <param name="fullyQualified">The fully qualified name to compare with.</param>
        /// <returns>true if the combination of <paramref name="namespaceName"/> and <paramref name="typeName"/> equals the fully-qualified name given by <paramref name="fullyQualified"/></returns>
        internal static bool SplitNameEqualsFullyQualifiedName(string namespaceName, string typeName, string fullyQualified)
        {
            // Look for "[namespaceName].[typeName]" exactly
            return fullyQualified.Length == namespaceName.Length + typeName.Length + 1 &&
                   fullyQualified[namespaceName.Length] == MetadataHelpers.DotDelimiter &&
                   fullyQualified.StartsWith(namespaceName, StringComparison.Ordinal) &&
                   fullyQualified.EndsWith(typeName, StringComparison.Ordinal);
        }
 
        internal static bool IsValidPublicKey(ImmutableArray<byte> bytes) => CryptoBlobParser.IsValidPublicKey(bytes);
 
        /// <summary>
        /// Given an input string changes it to be acceptable as a part of a type name.
        /// </summary>
        internal static string MangleForTypeNameIfNeeded(string moduleName)
        {
            var pooledStrBuilder = PooledStringBuilder.GetInstance();
            var s = pooledStrBuilder.Builder;
            s.Append(moduleName);
            s.Replace("Q", "QQ");
            s.Replace("_", "Q_");
            s.Replace('.', '_');
 
            return pooledStrBuilder.ToStringAndFree();
        }
    }
}