File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Reflection\Emit\TypeNameBuilder.cs
Web Access
Project: src\runtime\src\libraries\System.Reflection.Emit\src\System.Reflection.Emit.csproj (System.Reflection.Emit)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// This TypeNameBuilder is ported from CoreCLR's original.
// It replaces the C++ bits of the implementation with a faithful C# port.

using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace System.Reflection.Emit
{
    internal sealed class TypeNameBuilder
    {
        private readonly StringBuilder _str = new StringBuilder();
        private int _instNesting;
        private bool _firstInstArg;
        private bool _nestedName;
        private bool _hasAssemblySpec;
        private readonly List<int> _stack = new List<int>();
        private int _stackIdx;

        private TypeNameBuilder()
        {
        }

        private void OpenGenericArguments()
        {
            _instNesting++;
            _firstInstArg = true;

            Append('[');
        }

        private void CloseGenericArguments()
        {
            Debug.Assert(_instNesting != 0);

            _instNesting--;

            if (_firstInstArg)
            {
                _str.Remove(_str.Length - 1, 1);
            }
            else
            {
                Append(']');
            }
        }

        private void OpenGenericArgument()
        {
            Debug.Assert(_instNesting != 0);

            _nestedName = false;

            if (!_firstInstArg)
                Append(',');

            _firstInstArg = false;

            Append('[');

            PushOpenGenericArgument();
        }

        private void CloseGenericArgument()
        {
            Debug.Assert(_instNesting != 0);

            if (_hasAssemblySpec)
            {
                Append(']');
            }

            PopOpenGenericArgument();
        }

        private void AddName(string name)
        {
            Debug.Assert(name != null);

            if (_nestedName)
                Append('+');

            _nestedName = true;

            EscapeName(name);
        }

        private void AddArray(int rank)
        {
            Debug.Assert(rank > 0);

            if (rank == 1)
            {
                Append("[*]");
            }
            else if (rank > 64)
            {
                // Only taken in an error path, runtime will not load arrays of more than 32 dimensions
                _str.Append('[').Append(rank).Append(']');
            }
            else
            {
                Append('[');
                for (int i = 1; i < rank; i++)
                    Append(',');
                Append(']');
            }
        }

        private void AddAssemblySpec(string assemblySpec)
        {
            if (assemblySpec != null && !assemblySpec.Equals(""))
            {
                Append(", ");

                if (_instNesting > 0)
                {
                    EscapeEmbeddedAssemblyName(assemblySpec);
                }
                else
                {
                    EscapeAssemblyName(assemblySpec);
                }

                _hasAssemblySpec = true;
            }
        }

        public override string ToString()
        {
            Debug.Assert(_instNesting == 0);

            return _str.ToString();
        }

        private static bool ContainsReservedChar(string name)
        {
            foreach (char c in name)
            {
                if (c == '\0')
                    break;
                if (IsTypeNameReservedChar(c))
                    return true;
            }
            return false;
        }

        private static bool IsTypeNameReservedChar(char ch) =>
            ch is ',' or '[' or ']' or '&' or '*' or '+' or '\\';

        private void EscapeName(string name)
        {
            if (ContainsReservedChar(name))
            {
                foreach (char c in name)
                {
                    if (c == '\0')
                        break;
                    if (IsTypeNameReservedChar(c))
                        _str.Append('\\');
                    _str.Append(c);
                }
            }
            else
                Append(name);
        }

        private void EscapeAssemblyName(string name)
        {
            Append(name);
        }

        private void EscapeEmbeddedAssemblyName(string name)
        {
            if (name.Contains(']'))
            {
                foreach (char c in name)
                {
                    if (c == ']')
                        Append('\\');

                    Append(c);
                }
            }
            else
            {
                Append(name);
            }
        }

        private void PushOpenGenericArgument()
        {
            _stack.Add(_str.Length);
            _stackIdx++;
        }

        private void PopOpenGenericArgument()
        {
            int index = _stack[--_stackIdx];
            _stack.RemoveAt(_stackIdx);

            if (!_hasAssemblySpec)
                _str.Remove(index - 1, 1);

            _hasAssemblySpec = false;
        }

        private void Append(string pStr)
        {
            int i = pStr.IndexOf('\0');
            if (i < 0)
            {
                _str.Append(pStr);
            }
            else if (i > 0)
            {
                _str.Append(pStr.AsSpan(0, i));
            }
        }

        private void Append(char c)
        {
            _str.Append(c);
        }

        internal enum Format
        {
            ToString,
            FullName,
            AssemblyQualifiedName,
        }

        internal static string? ToString(Type type, Format format)
        {
            if (format == Format.FullName || format == Format.AssemblyQualifiedName)
            {
                if (!type.IsGenericTypeDefinition && type.ContainsGenericParameters)
                    return null;
            }

            var tnb = new TypeNameBuilder();
            tnb.AddAssemblyQualifiedName(type, format);
            return tnb.ToString();
        }

        private void AddElementType(Type type)
        {
            if (!type.HasElementType)
                return;

            AddElementType(type.GetElementType()!);

            if (type.IsPointer)
                Append('*');
            else if (type.IsByRef)
                Append('&');
            else if (type.IsSZArray)
                Append("[]");
            else if (type.IsArray)
                AddArray(type.GetArrayRank());
        }

        private void AddAssemblyQualifiedName(Type type, Format format)
        {
            Type rootType = type;

            while (rootType.HasElementType)
                rootType = rootType.GetElementType()!;

            // Append namespace + nesting + name
            var nestings = new List<Type>();
            for (Type? t = rootType; t != null; t = t.IsGenericParameter ? null : t.DeclaringType)
                nestings.Add(t);

            for (int i = nestings.Count - 1; i >= 0; i--)
            {
                Type enclosingType = nestings[i];
                string name = enclosingType.Name;

                if (i == nestings.Count - 1 && !string.IsNullOrEmpty(enclosingType.Namespace))
                    name = enclosingType.Namespace + "." + name;

                AddName(name);
            }

            // Append generic arguments
            if (rootType.IsGenericType && (!rootType.IsGenericTypeDefinition || format == Format.ToString))
            {
                Type[] genericArguments = rootType.GetGenericArguments();

                OpenGenericArguments();
                for (int i = 0; i < genericArguments.Length; i++)
                {
                    Format genericArgumentsFormat = format == Format.FullName ? Format.AssemblyQualifiedName : format;

                    OpenGenericArgument();
                    AddAssemblyQualifiedName(genericArguments[i], genericArgumentsFormat);
                    CloseGenericArgument();
                }
                CloseGenericArguments();
            }

            // Append pointer, byRef and array qualifiers
            AddElementType(type);

            if (format == Format.AssemblyQualifiedName)
                AddAssemblySpec(type.Module.Assembly.FullName!);
        }
    }
}