Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add signature parser based on Roslyn doc comments #1197

Merged
merged 9 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions src/linker/Linker/DocumentationSignatureGenerator.PartVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// 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.

using System;
sbomer marked this conversation as resolved.
Show resolved Hide resolved
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Mono.Cecil;

namespace Mono.Linker
{

public 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)
{
VisitTypeReference (arrayType.ElementType, builder);

// 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)
{
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.HasParameters || (method.CallingConvention == MethodCallingConvention.VarArg))
VisitParameters (method.Parameters, method.CallingConvention == MethodCallingConvention.VarArg, builder);

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));
}

public 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)
{
switch (typeReference) {
case ByReferenceType byReferenceType:
VisitByReferenceType (byReferenceType, builder);
return;
case PointerType pointerType:
VisitPointerType (pointerType, builder);
return;
case ArrayType arrayType:
VisitArrayType (arrayType, builder);
return;
case GenericParameter genericParameter:
VisitGenericParameter (genericParameter, builder);
return;
}

if (typeReference.IsNested) {
VisitTypeReference (typeReference.GetInflatedDeclaringType (), builder);
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.IsGenericInstance)
return;

var genericInstance = typeReference as GenericInstanceType;

// 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);
needsComma = true;
}
builder.Append ('}');
}

public void VisitPointerType (PointerType pointerType, StringBuilder builder)
{
VisitTypeReference (pointerType.ElementType, builder);
builder.Append ('*');
}

public void VisitByReferenceType (ByReferenceType byReferenceType, StringBuilder builder)
{
VisitTypeReference (byReferenceType.ElementType, builder);
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 ('>', '}');
}
}
}
}
55 changes: 55 additions & 0 deletions src/linker/Linker/DocumentationSignatureGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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.

using System;
sbomer marked this conversation as resolved.
Show resolved Hide resolved
using System.Text;
using Mono.Cecil;

namespace Mono.Linker
{
/// <summary>
/// Generates a signature for a member, in the format used for C# Documentation Comments:
/// https://github.com/dotnet/csharplang/blob/master/spec/documentation-comments.md#id-string-format
/// Adapted from Roslyn's DocumentationCommentIDVisitor:
/// https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/DocumentationComments/DocumentationCommentIDVisitor.cs
/// </summary>
public sealed partial class DocumentationSignatureGenerator
{
public static readonly DocumentationSignatureGenerator Instance = new DocumentationSignatureGenerator ();

private DocumentationSignatureGenerator ()
{
}

public void VisitMethod (MethodDefinition method, StringBuilder builder)
{
builder.Append ("M:");
PartVisitor.Instance.VisitMethodDefinition (method, builder);
}

public void VisitField (FieldDefinition field, StringBuilder builder)
{
builder.Append ("F:");
PartVisitor.Instance.VisitField (field, builder);
}

public void VisitEvent (EventDefinition evt, StringBuilder builder)
{
builder.Append ("E:");
PartVisitor.Instance.VisitEvent (evt, builder);
}

public void VisitProperty (PropertyDefinition property, StringBuilder builder)
{
builder.Append ("P:");
PartVisitor.Instance.VisitProperty (property, builder);
}

public void VisitTypeDefinition (TypeDefinition type, StringBuilder builder)
{
builder.Append ("T:");
PartVisitor.Instance.VisitTypeReference (type, builder);
}
}
}
Loading