|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Mono.Cecil;
namespace Mono.Linker
{
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>
internal sealed class PartVisitor
{
internal static readonly PartVisitor Instance = new PartVisitor();
private PartVisitor()
{
}
public void VisitArrayType(ArrayType arrayType, StringBuilder builder, ITryResolveMetadata resolver)
{
VisitTypeReference(arrayType.ElementType, builder, resolver);
// Rank-one arrays are displayed different than rectangular arrays
if (arrayType.IsVector)
{
builder.Append("[]");
}
else
{
// C# arrays only support zero lower bounds
if (arrayType.Dimensions[0].LowerBound != 0)
throw new NotImplementedException();
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(']');
}
}
public void VisitField(FieldDefinition field, StringBuilder builder, ITryResolveMetadata resolver)
{
VisitTypeReference(field.DeclaringType, builder, resolver);
builder.Append('.').Append(field.Name);
}
private void VisitParameters(IEnumerable<ParameterDefinition> parameters, bool isVararg, StringBuilder builder, ITryResolveMetadata resolver)
{
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, resolver);
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, ITryResolveMetadata resolver)
{
VisitTypeReference(method.DeclaringType, builder, resolver);
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, resolver);
#pragma warning restore RS0030
if (method.Name == "op_Implicit" || method.Name == "op_Explicit")
{
builder.Append('~');
VisitTypeReference(method.ReturnType, builder, resolver);
}
}
public void VisitProperty(PropertyDefinition property, StringBuilder builder, ITryResolveMetadata resolver)
{
VisitTypeReference(property.DeclaringType, builder, resolver);
builder.Append('.').Append(GetEscapedMetadataName(property));
if (property.Parameters.Count > 0)
VisitParameters(property.Parameters, false, builder, resolver);
}
public void VisitEvent(EventDefinition evt, StringBuilder builder, ITryResolveMetadata resolver)
{
VisitTypeReference(evt.DeclaringType, builder, resolver);
builder.Append('.').Append(GetEscapedMetadataName(evt));
}
public static void VisitGenericParameter(GenericParameter genericParameter, StringBuilder builder)
{
Debug.Assert(genericParameter.DeclaringMethod == null ^ genericParameter.DeclaringType == null);
// Is this a type parameter on a type?
if (genericParameter.DeclaringMethod != null)
{
builder.Append("``");
}
else
{
Debug.Assert(genericParameter.DeclaringType != null);
// 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.Position);
}
public void VisitTypeReference(TypeReference typeReference, StringBuilder builder, ITryResolveMetadata resolver)
{
switch (typeReference)
{
case ByReferenceType byReferenceType:
VisitByReferenceType(byReferenceType, builder, resolver);
return;
case PointerType pointerType:
VisitPointerType(pointerType, builder, resolver);
return;
case ArrayType arrayType:
VisitArrayType(arrayType, builder, resolver);
return;
case GenericParameter genericParameter:
VisitGenericParameter(genericParameter, builder);
return;
}
if (typeReference.IsNested)
{
Debug.Assert(typeReference is not SentinelType && typeReference is not PinnedType);
// GetInflatedDeclaringType may return null for generic parameters, byrefs, and pointers, but these
// are separately handled above.
VisitTypeReference(typeReference.GetInflatedDeclaringType(resolver)!, builder, resolver);
builder.Append('.');
}
if (!string.IsNullOrEmpty(typeReference.Namespace))
builder.Append(typeReference.Namespace).Append('.');
// This includes '`n' for mangled generic types
builder.Append(typeReference.Name);
// For uninstantiated generic types (we already built the mangled name)
// or non-generic types, we are done.
if (typeReference.HasGenericParameters || typeReference is not GenericInstanceType genericInstance)
return;
// Compute arity counting only the newly-introduced generic parameters
var declaringType = genericInstance.DeclaringType;
var declaringArity = 0;
if (declaringType != null && declaringType.HasGenericParameters)
declaringArity = declaringType.GenericParameters.Count;
var totalArity = genericInstance.GenericArguments.Count;
var arity = totalArity - declaringArity;
// Un-mangle the generic type name
var suffixLength = arity.ToString().Length + 1;
builder.Remove(builder.Length - suffixLength, suffixLength);
// Append type arguments excluding arguments for re-declared parent generic parameters
builder.Append('{');
bool needsComma = false;
for (int i = totalArity - arity; i < totalArity; ++i)
{
if (needsComma)
builder.Append(',');
var typeArgument = genericInstance.GenericArguments[i];
VisitTypeReference(typeArgument, builder, resolver);
needsComma = true;
}
builder.Append('}');
}
public void VisitPointerType(PointerType pointerType, StringBuilder builder, ITryResolveMetadata resolver)
{
VisitTypeReference(pointerType.ElementType, builder, resolver);
builder.Append('*');
}
public void VisitByReferenceType(ByReferenceType byReferenceType, StringBuilder builder, ITryResolveMetadata resolver)
{
VisitTypeReference(byReferenceType.ElementType, builder, resolver);
builder.Append('@');
}
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('>', '}');
}
}
}
}
|