File: Hosting\ObjectFormatter\CommonTypeNameFormatter.cs
Web Access
Project: src\src\Scripting\Core\Microsoft.CodeAnalysis.Scripting.csproj (Microsoft.CodeAnalysis.Scripting)
// 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.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Collections;
 
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
    using static ObjectFormatterHelpers;
    using TypeInfo = System.Reflection.TypeInfo;
 
    internal abstract partial class CommonTypeNameFormatter
    {
        protected abstract string GetPrimitiveTypeName(SpecialType type);
 
        protected abstract string GenericParameterOpening { get; }
        protected abstract string GenericParameterClosing { get; }
 
        protected abstract string ArrayOpening { get; }
        protected abstract string ArrayClosing { get; }
 
        protected abstract CommonPrimitiveFormatter PrimitiveFormatter { get; }
 
        // TODO (tomat): Use DebuggerDisplay.Type if specified?
        public virtual string FormatTypeName(Type type, CommonTypeNameFormatterOptions options)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }
 
            string primitiveTypeName = GetPrimitiveTypeName(GetPrimitiveSpecialType(type));
            if (primitiveTypeName != null)
            {
                return primitiveTypeName;
            }
 
            if (type.IsGenericParameter)
            {
                return type.Name;
            }
 
            if (type.IsArray)
            {
                return FormatArrayTypeName(type, arrayOpt: null, options: options);
            }
 
            var typeInfo = type.GetTypeInfo();
            if (typeInfo.IsGenericType)
            {
                return FormatGenericTypeName(typeInfo, options);
            }
 
            return FormatNonGenericTypeName(typeInfo, options);
        }
 
        private static string FormatNonGenericTypeName(TypeInfo typeInfo, CommonTypeNameFormatterOptions options)
        {
            if (options.ShowNamespaces)
            {
                return typeInfo.FullName.Replace('+', '.');
            }
 
            if (typeInfo.DeclaringType == null)
            {
                return typeInfo.Name;
            }
 
            var stack = ArrayBuilder<string>.GetInstance();
 
            do
            {
                stack.Push(typeInfo.Name);
                typeInfo = typeInfo.DeclaringType?.GetTypeInfo();
            } while (typeInfo != null);
 
            stack.ReverseContents();
            var typeName = string.Join(".", stack);
            stack.Free();
 
            return typeName;
        }
 
        public virtual string FormatTypeArguments(Type[] typeArguments, CommonTypeNameFormatterOptions options)
        {
            if (typeArguments == null)
            {
                throw new ArgumentNullException(nameof(typeArguments));
            }
 
            if (typeArguments.Length == 0)
            {
                throw new ArgumentException(null, nameof(typeArguments));
            }
 
            var pooled = PooledStringBuilder.GetInstance();
            var builder = pooled.Builder;
 
            builder.Append(GenericParameterOpening);
 
            var first = true;
            foreach (var typeArgument in typeArguments)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    builder.Append(", ");
                }
 
                builder.Append(FormatTypeName(typeArgument, options));
            }
 
            builder.Append(GenericParameterClosing);
 
            return pooled.ToStringAndFree();
        }
 
        /// <summary>
        /// Formats an array type name (vector or multidimensional).
        /// </summary>
        public virtual string FormatArrayTypeName(Type arrayType, Array arrayOpt, CommonTypeNameFormatterOptions options)
        {
            if (arrayType == null)
            {
                throw new ArgumentNullException(nameof(arrayType));
            }
 
            StringBuilder sb = new StringBuilder();
 
            // print the inner-most element type first:
            Type elementType = arrayType.GetElementType();
            while (elementType.IsArray)
            {
                elementType = elementType.GetElementType();
            }
 
            sb.Append(FormatTypeName(elementType, options));
 
            // print all components of a jagged array:
            Type type = arrayType;
            do
            {
                if (arrayOpt != null)
                {
                    sb.Append(ArrayOpening);
 
                    int rank = type.GetArrayRank();
 
                    bool anyNonzeroLowerBound = false;
                    for (int i = 0; i < rank; i++)
                    {
                        if (arrayOpt.GetLowerBound(i) > 0)
                        {
                            anyNonzeroLowerBound = true;
                            break;
                        }
                    }
 
                    for (int i = 0; i < rank; i++)
                    {
                        int lowerBound = arrayOpt.GetLowerBound(i);
                        int length = arrayOpt.GetLength(i);
 
                        if (i > 0)
                        {
                            sb.Append(", ");
                        }
 
                        if (anyNonzeroLowerBound)
                        {
                            AppendArrayBound(sb, lowerBound, options.ArrayBoundRadix);
                            sb.Append("..");
                            AppendArrayBound(sb, length + lowerBound, options.ArrayBoundRadix);
                        }
                        else
                        {
                            AppendArrayBound(sb, length, options.ArrayBoundRadix);
                        }
                    }
 
                    sb.Append(ArrayClosing);
                    arrayOpt = null;
                }
                else
                {
                    AppendArrayRank(sb, type);
                }
 
                type = type.GetElementType();
            }
            while (type.IsArray);
 
            return sb.ToString();
        }
 
        private void AppendArrayBound(StringBuilder sb, long bound, int numberRadix)
        {
            var options = new CommonPrimitiveFormatterOptions(
                numberRadix: numberRadix,
                includeCodePoints: false,
                quoteStringsAndCharacters: true,
                escapeNonPrintableCharacters: true,
                cultureInfo: CultureInfo.InvariantCulture);
            var formatted = bound is >= int.MinValue and <= int.MaxValue
                ? PrimitiveFormatter.FormatPrimitive((int)bound, options)
                : PrimitiveFormatter.FormatPrimitive(bound, options);
            sb.Append(formatted);
        }
 
        private void AppendArrayRank(StringBuilder sb, Type arrayType)
        {
            sb.Append(ArrayOpening);
            int rank = arrayType.GetArrayRank();
            if (rank > 1)
            {
                sb.Append(',', rank - 1);
            }
            sb.Append(ArrayClosing);
        }
 
        private string FormatGenericTypeName(TypeInfo typeInfo, CommonTypeNameFormatterOptions options)
        {
            var pooledBuilder = PooledStringBuilder.GetInstance();
            var builder = pooledBuilder.Builder;
 
            // consolidated generic arguments (includes arguments of all declaring types):
            // TODO (DevDiv #173210): shouldn't need parameters, but StackTrace gives us unconstructed symbols.
            Type[] genericArguments = typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters : typeInfo.GenericTypeArguments;
 
            if (typeInfo.DeclaringType != null)
            {
                var nestedTypes = ArrayBuilder<TypeInfo>.GetInstance();
                do
                {
                    nestedTypes.Add(typeInfo);
                    typeInfo = typeInfo.DeclaringType?.GetTypeInfo();
                }
                while (typeInfo != null);
 
                if (options.ShowNamespaces)
                {
                    var @namespace = nestedTypes.Last().Namespace;
                    if (@namespace != null)
                    {
                        builder.Append(@namespace + ".");
                    }
                }
 
                int typeArgumentIndex = 0;
                for (int i = nestedTypes.Count - 1; i >= 0; i--)
                {
                    AppendTypeInstantiation(builder, nestedTypes[i], genericArguments, ref typeArgumentIndex, options);
                    if (i > 0)
                    {
                        builder.Append('.');
                    }
                }
 
                nestedTypes.Free();
            }
            else
            {
                int typeArgumentIndex = 0;
                AppendTypeInstantiation(builder, typeInfo, genericArguments, ref typeArgumentIndex, options);
            }
 
            return pooledBuilder.ToStringAndFree();
        }
 
        private void AppendTypeInstantiation(
            StringBuilder builder,
            TypeInfo typeInfo,
            Type[] genericArguments,
            ref int genericArgIndex,
            CommonTypeNameFormatterOptions options)
        {
            // generic arguments of all the outer types and the current type;
            int currentArgCount = (typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters.Length : typeInfo.GenericTypeArguments.Length) - genericArgIndex;
 
            if (currentArgCount > 0)
            {
                string name = typeInfo.Name;
 
                int backtick = name.IndexOf('`');
                if (backtick > 0)
                {
                    builder.Append(name[..backtick]);
                }
                else
                {
                    builder.Append(name);
                }
 
                builder.Append(GenericParameterOpening);
 
                for (int i = 0; i < currentArgCount; i++)
                {
                    if (i > 0)
                    {
                        builder.Append(", ");
                    }
                    builder.Append(FormatTypeName(genericArguments[genericArgIndex++], options));
                }
 
                builder.Append(GenericParameterClosing);
            }
            else
            {
                builder.Append(typeInfo.Name);
            }
        }
    }
}