forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add signature parser based on Roslyn doc comments (dotnet/linker#1197)
* Add signature parser based on Roslyn doc comments * Fix formatting * PR feedback - SignatureParser -> DocumentationSignatureParser - GetSignaturePart extension moved to DocumentationSignatureParser - Inverted a condition - Added a clarifying comment about generics * Avoid ImmutableArray To let it build on mono * Rename test attributes And add comments clarifying their behavior * Fix mono test failure There were two issues: - Nunit doesn't like assertions inside of a TestCaseSource method. - On mono, the test infra failed to resolve the new assertion attributes * Factor parser for use from DynamicDependencyAttribute - Allow parsing a signature without a member type prefix - Allow parsing a member without a type name - Allow matching multiple member kinds - Rename some public methods to more closely match linker terminology - Add a few more tests for edge cases * PR feedback - License headers - Don't use string interpolation - Don't use extension method for GetSignaturePart - Remove unnecessary nullable context - Remove unnecessary IndexOf * Update link for edge cases Commit migrated from dotnet/linker@0e38c61
- Loading branch information
Showing
11 changed files
with
1,679 additions
and
0 deletions.
There are no files selected for viewing
213 changes: 213 additions & 0 deletions
213
src/tools/illink/src/linker/Linker/DocumentationSignatureGenerator.PartVisitor.cs
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,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 ('>', '}'); | ||
} | ||
} | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/tools/illink/src/linker/Linker/DocumentationSignatureGenerator.cs
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,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; | ||
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.