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 1 commit
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
207 changes: 207 additions & 0 deletions src/linker/Linker/SignatureGenerator.PartVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Mono.Cecil;

namespace Mono.Linker
{

public sealed partial class SignatureGenerator
{
/// <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);

if (typeReference.HasGenericParameters || !typeReference.IsGenericInstance)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this condition correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so - unless I missed a case in cecil. Non-generics don't need more work, and uninstantiated generics are already mangled. I added a clarifying comment.

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 ('>', '}');
}
}
}
}
53 changes: 53 additions & 0 deletions src/linker/Linker/SignatureGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Text;
using Mono.Cecil;

#nullable enable

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 SignatureGenerator
{
public static readonly SignatureGenerator Instance = new SignatureGenerator ();

private SignatureGenerator ()
{
}

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