|
// 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 ('>', '}');
}
}
}
}
|