File: Compiler\Logging\DocumentationSignatureGenerator.PartVisitor.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text;

using Internal.TypeSystem;

namespace ILCompiler.Logging
{
    internal sealed partial class DocumentationSignatureGenerator
    {
        /// <summary>
        ///  A visitor that generates the part of the documentation comment after the initial type
        ///  and colon.
        ///  Adapted from Roslyn's DocumentattionCommentIDVisitor.PartVisitor:
        ///  https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/DocumentationComments/DocumentationCommentIDVisitor.PartVisitor.cs
        /// </summary>
        public sealed class PartVisitor : TypeNameFormatter
        {
            public static readonly PartVisitor Instance = new PartVisitor();

            private PartVisitor()
            {
            }

            public override void AppendName(StringBuilder builder, ArrayType arrayType)
            {
                AppendName(builder, arrayType.ElementType);

                // Rank-one arrays are displayed different than rectangular arrays
                if (arrayType.IsSzArray)
                {
                    builder.Append("[]");
                }
                else
                {
                    // C# arrays only support zero lower bounds
                    builder.Append("[0:");
                    for (int i = 1; i < arrayType.Rank; i++)
                    {
                        //if (arrayType.Dimensions[0].LowerBound != 0)
                        //    throw new NotImplementedException();
                        builder.Append(",0:");
                    }

                    builder.Append(']');
                }
            }

#if false
            public void VisitField(FieldDefinition field, StringBuilder builder)
            {
                VisitTypeReference(field.DeclaringType, builder);
                builder.Append('.').Append(field.Name);
            }

            private void VisitParameters(IEnumerable<ParameterDefinition> parameters, bool isVararg, StringBuilder builder)
            {
                builder.Append('(');
                bool needsComma = false;

                foreach (var parameter in parameters)
                {
                    if (needsComma)
                        builder.Append(',');

                    // byrefs are tracked on the parameter type, not the parameter,
                    // so we don't have VisitParameter that Roslyn uses.
                    VisitTypeReference(parameter.ParameterType, builder);
                    needsComma = true;
                }

                // note: the C# doc comment generator outputs an extra comma for varargs
                // methods that also have fixed parameters
                if (isVararg && needsComma)
                    builder.Append(',');

                builder.Append(')');
            }

            public void VisitMethodDefinition(MethodDefinition method, StringBuilder builder)
            {
                VisitTypeReference(method.DeclaringType, builder);
                builder.Append('.').Append(GetEscapedMetadataName(method));

                if (method.HasGenericParameters)
                    builder.Append("``").Append(method.GenericParameters.Count);

                if (method.HasMetadataParameters() || (method.CallingConvention == MethodCallingConvention.VarArg))
#pragma warning disable RS0030 // MethodReference.Parameters is banned. This generates documentation signatures, so it's okay to use it here
                    VisitParameters(method.Parameters, method.CallingConvention == MethodCallingConvention.VarArg, builder);
#pragma warning restore RS0030

                if (method.Name == "op_Implicit" || method.Name == "op_Explicit")
                {
                    builder.Append('~');
                    VisitTypeReference(method.ReturnType, builder);
                }
            }

            public void VisitProperty(PropertyDefinition property, StringBuilder builder)
            {
                VisitTypeReference(property.DeclaringType, builder);
                builder.Append('.').Append(GetEscapedMetadataName(property));

                if (property.Parameters.Count > 0)
                    VisitParameters(property.Parameters, false, builder);
            }

            public void VisitEvent(EventDefinition evt, StringBuilder builder)
            {
                VisitTypeReference(evt.DeclaringType, builder);
                builder.Append('.').Append(GetEscapedMetadataName(evt));
            }
#endif

            public override void AppendName(StringBuilder builder, FunctionPointerType type)
            {
                // Not defined how this should look like
                // https://github.com/dotnet/roslyn/issues/48363
            }

            public override void AppendName(StringBuilder builder, GenericParameterDesc genericParameter)
            {
                // Is this a type parameter on a type?
                if (genericParameter.Kind == GenericParameterKind.Method)
                {
                    builder.Append("``");
                }
                else
                {
                    Debug.Assert(genericParameter.Kind == GenericParameterKind.Type);

                    // If the containing type is nested within other types.
                    // e.g. A<T>.B<U>.M<V>(T t, U u, V v) should be M(`0, `1, ``0).
                    // Roslyn needs to add generic arities of parents, but the innermost type redeclares
                    // all generic parameters so we don't need to add them.
                    builder.Append('`');
                }

                builder.Append(genericParameter.Index);
            }

            public override void AppendName(StringBuilder builder, SignatureMethodVariable type) => builder.Append("``").Append(type.Index);
            public override void AppendName(StringBuilder builder, SignatureTypeVariable type) => builder.Append('`').Append(type.Index);

            protected override void AppendNameForNestedType(StringBuilder sb, DefType nestedType, DefType containingType)
            {
                AppendName(sb, InstantiateContainingType(nestedType));
                sb.Append('.');
                sb.Append(nestedType.GetName());
            }

            protected override void AppendNameForNamespaceType(StringBuilder sb, DefType type)
            {
                string @namespace = type.GetNamespace();
                if (!string.IsNullOrEmpty(@namespace))
                    sb.Append(@namespace).Append('.');
                sb.Append(type.GetName());
            }

            protected override void AppendNameForInstantiatedType(StringBuilder builder, DefType type)
            {
                int containingArity = 0;
                DefType containingType = InstantiateContainingType(type);
                if (containingType != null)
                {
                    AppendName(builder, containingType);
                    builder.Append('.');
                    containingArity = containingType.Instantiation.Length;
                }
                else
                {
                    string @namespace = type.GetNamespace();
                    if (!string.IsNullOrEmpty(@namespace))
                        builder.Append(@namespace).Append('.');
                }

                string unmangledName = type.GetName();
                int totalArity = type.Instantiation.Length;
                int nestedArity = totalArity - containingArity;
                string expectedSuffix = $"`{nestedArity}";
                if (unmangledName.EndsWith(expectedSuffix))
                    unmangledName = unmangledName.Substring(0, unmangledName.Length - expectedSuffix.Length);

                builder.Append(unmangledName);

                // Append type arguments excluding arguments for re-declared parent generic parameters
                builder.Append('{');
                bool needsComma = false;
                for (int i = containingArity; i < totalArity; ++i)
                {
                    if (needsComma)
                        builder.Append(',');
                    var typeArgument = type.Instantiation[i];
                    AppendName(builder, typeArgument);
                    needsComma = true;
                }
                builder.Append('}');
            }

            public override void AppendName(StringBuilder builder, PointerType type)
            {
                AppendName(builder, type.ParameterType);
                builder.Append('*');
            }

            public override void AppendName(StringBuilder builder, ByRefType type)
            {
                AppendName(builder, type.ParameterType);
                builder.Append('@');
            }

#if false
            private static string GetEscapedMetadataName(IMemberDefinition member)
            {
                var name = member.Name.Replace('.', '#');
                // Not sure if the following replacements are necessary, but
                // they are included to match Roslyn.
                return name.Replace('<', '{').Replace('>', '}');
            }
#endif

            private static DefType InstantiateContainingType(DefType type)
            {
                DefType containingType = type.ContainingType;
                if (containingType is null)
                    return null;

                // If the type doesn't follow C# scheme where nested types inherit generic parameters from their container type
                // return the container type as-is.
                if (!containingType.HasInstantiation || containingType.Instantiation.Length > type.Instantiation.Length)
                    return containingType;

                return (DefType)containingType.InstantiateAsOpen().InstantiateSignature(type.Instantiation, default);
            }
        }
    }
}