-
Notifications
You must be signed in to change notification settings - Fork 127
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
Changes from 1 commit
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
6194fd4
Add signature parser based on Roslyn doc comments
sbomer 6481bba
Fix formatting
sbomer 9deb282
PR feedback
sbomer b22e8ed
Avoid ImmutableArray
sbomer a3c12da
Rename test attributes
sbomer ebc03db
Fix mono test failure
sbomer 647924d
Factor parser for use from DynamicDependencyAttribute
sbomer c8613eb
PR feedback
sbomer f016525
Update link for edge cases
sbomer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
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 ('>', '}'); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this condition correct?
There was a problem hiding this comment.
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.