-
Notifications
You must be signed in to change notification settings - Fork 127
Add signature parser based on Roslyn doc comments #1197
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
Merged
Merged
Changes from all commits
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
213 changes: 213 additions & 0 deletions
213
src/linker/Linker/DocumentationSignatureGenerator.PartVisitor.cs
This file contains hidden or 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,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; | ||
| 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 ('>', '}'); | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or 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,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.
|
||
| 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); | ||
| } | ||
| } | ||
| } | ||
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.
Uh oh!
There was an error while loading. Please reload this page.