Skip to content
Closed
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
105 changes: 96 additions & 9 deletions rocks/Mono.Cecil.Rocks/DocCommentId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mono.Cecil.Rocks {

public class DocCommentId
{
IMemberDefinition commentMember;
StringBuilder id;

DocCommentId ()
DocCommentId (IMemberDefinition member)
{
commentMember = member;
id = new StringBuilder ();
}

Expand Down Expand Up @@ -103,6 +106,8 @@ void WriteTypeSignature (TypeReference type)
WriteGenericInstanceTypeSignature ((GenericInstanceType) type);
break;
case MetadataType.Var:
if (IsGenericMethodTypeParameter (type))
id.Append ('`');
id.Append ('`');
id.Append (((GenericParameter) type).Position);
break;
Expand All @@ -126,15 +131,26 @@ void WriteTypeSignature (TypeReference type)
}
}

bool IsGenericMethodTypeParameter (TypeReference type)
{
if (commentMember is MethodDefinition methodDefinition && type is GenericParameter genericParameter)
return methodDefinition.GenericParameters.Any (i => i.Name == genericParameter.Name);

return false;
}

void WriteGenericInstanceTypeSignature (GenericInstanceType type)
{
if (type.ElementType.IsTypeSpecification ())
throw new NotSupportedException ();

WriteTypeFullName (type.ElementType, stripGenericArity: true);
id.Append ('{');
WriteList (type.GenericArguments, WriteTypeSignature);
id.Append ('}');
GenericTypeOptions options = new GenericTypeOptions {
IsArgument = true,
IsNestedType = type.IsNested,
Arguments = type.GenericArguments
};

WriteTypeFullName (type.ElementType, options);
}

void WriteList<T> (IList<T> list, Action<T> action)
Expand Down Expand Up @@ -197,10 +213,15 @@ void WriteDefinition (char id, IMemberDefinition member)
WriteItemName (member.Name);
}

void WriteTypeFullName (TypeReference type, bool stripGenericArity = false)
void WriteTypeFullName (TypeReference type)
{
WriteTypeFullName (type, GenericTypeOptions.Empty ());
}

void WriteTypeFullName (TypeReference type, GenericTypeOptions options)
{
if (type.DeclaringType != null) {
WriteTypeFullName (type.DeclaringType);
WriteTypeFullName (type.DeclaringType, options);
id.Append ('.');
}

Expand All @@ -211,13 +232,64 @@ void WriteTypeFullName (TypeReference type, bool stripGenericArity = false)

var name = type.Name;

if (stripGenericArity) {
if (options.IsArgument) {
var index = name.LastIndexOf ('`');
if (index > 0)
name = name.Substring (0, index);
}

id.Append (name);

WriteGenericTypeParameters (type, options);
}

void WriteGenericTypeParameters (TypeReference type, GenericTypeOptions options)
{
if (options.IsArgument && IsGenericType (type)) {
id.Append ('{');
WriteList (GetGenericTypeArguments (type, options), WriteTypeSignature);
id.Append ('}');
}
}

static bool IsGenericType (TypeReference type)
{
// When the type is a nested type and that is defined in a generic class,
// the nested type will have generic parameters but sometimes that is not a generic type.
if (type.HasGenericParameters) {
var name = string.Empty;
var index = type.Name.LastIndexOf ('`');
if (index >= 0)
name = type.Name.Substring (0, index);

return type.Name.LastIndexOf ('`') == name.Length;
}

return false;
}

IList<TypeReference> GetGenericTypeArguments (TypeReference type, GenericTypeOptions options)
{
if (options.IsNestedType) {
var typeParameterCount = GetGenericTypeParameterCount (type);
var typeGenericArguments = options.Arguments.Skip (options.ArgumentIndex).Take (typeParameterCount).ToList ();

options.ArgumentIndex += typeParameterCount;

return typeGenericArguments;
}

return options.Arguments;
}

int GetGenericTypeParameterCount (TypeReference type)
{
var returnValue = 0;
Copy link
Owner

Choose a reason for hiding this comment

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

Doesn't type.GenericParameters.Count work here?

var index = type.Name.LastIndexOf ('`');
if (index >= 0)
returnValue = int.Parse (type.Name.Substring (index + 1));

return returnValue;
}

void WriteItemName (string name)
Expand All @@ -235,7 +307,7 @@ public static string GetDocCommentId (IMemberDefinition member)
if (member == null)
throw new ArgumentNullException ("member");

var documentId = new DocCommentId ();
var documentId = new DocCommentId (member);

switch (member.MetadataToken.TokenType)
{
Expand All @@ -260,5 +332,20 @@ public static string GetDocCommentId (IMemberDefinition member)

return documentId.ToString ();
}

class GenericTypeOptions {
public bool IsArgument { get; set; }

public bool IsNestedType { get; set; }

public IList<TypeReference> Arguments { get; set; }

public int ArgumentIndex { get; set; }

public static GenericTypeOptions Empty ()
{
return new GenericTypeOptions ();
}
}
}
}
103 changes: 101 additions & 2 deletions rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;

using NUnit.Framework;

using Mono.Cecil.Rocks;

namespace N
Expand Down Expand Up @@ -116,6 +114,76 @@ public interface IX<K>
}

public class KVP<K, T> { }

public class GenericMethod
{
/// <summary>
/// ID string generated is "M:N.GenericMethod.WithNestedType``1(N.GenericType{``0}.NestedType)".
/// </summary>
public void WithNestedType<T> (GenericType<T>.NestedType nestedType) { }


/// <summary>
/// ID string generated is "M:N.GenericMethod.WithIntOfNestedType``1(N.GenericType{System.Int32}.NestedType)".
/// </summary>
public void WithIntOfNestedType<T> (GenericType<int>.NestedType nestedType) { }


/// <summary>
/// ID string generated is "M:N.GenericMethod.WithNestedGenericType``1(N.GenericType{``0}.NestedGenericType{``0}.NestedType)".
/// </summary>
public void WithNestedGenericType<T> (GenericType<T>.NestedGenericType<T>.NestedType nestedType) { }


/// <summary>
/// ID string generated is "M:N.GenericMethod.WithIntOfNestedGenericType``1(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)".
/// </summary>
public void WithIntOfNestedGenericType<T> (GenericType<int>.NestedGenericType<int>.NestedType nestedType) { }


/// <summary>
/// ID string generated is "M:N.GenericMethod.WithMultipleTypeParameterAndNestedGenericType``2(N.GenericType{``0}.NestedGenericType{``1}.NestedType)".
/// </summary>
public void WithMultipleTypeParameterAndNestedGenericType<T1, T2> (GenericType<T1>.NestedGenericType<T2>.NestedType nestedType) { }


/// <summary>
/// ID string generated is "M:N.GenericMethod.WithMultipleTypeParameterAndIntOfNestedGenericType``2(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)".
/// </summary>
public void WithMultipleTypeParameterAndIntOfNestedGenericType<T1, T2> (GenericType<int>.NestedGenericType<int>.NestedType nestedType) { }
}

public class GenericType<T>
{
public class NestedType { }

public class NestedGenericType<TNested> {
public class NestedType { }

/// <summary>
/// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericMethod``1(System.Collections.Generic.List{``0})"
/// </summary>
public void WithTypeParameterOfGenericMethod<TMethod> (List<TMethod> list) { }


/// <summary>
/// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericType(System.Collections.Generic.Dictionary{`0,`1})"
/// </summary>
public void WithTypeParameterOfGenericType (Dictionary<T, TNested> dict) { }


/// <summary>
/// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericType``1(System.Collections.Generic.List{`1})"
/// </summary>
public void WithTypeParameterOfNestedGenericType<TMethod> (List<TNested> list) { }


/// <summary>
/// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericTypeAndGenericMethod``1(System.Collections.Generic.Dictionary{`1,``0})"
/// </summary>
public void WithTypeParameterOfGenericTypeAndGenericMethod<TMethod> (Dictionary<TNested, TMethod> dict) { }
}
}
}

namespace Mono.Cecil.Tests {
Expand Down Expand Up @@ -192,6 +260,32 @@ public void MethodWithArrayParameters ()
AssertDocumentID ("M:N.X.gg(System.Int16[],System.Int32[0:,0:])", method);
}

[TestCase ("WithNestedType", "WithNestedType``1(N.GenericType{``0}.NestedType)")]
[TestCase ("WithIntOfNestedType", "WithIntOfNestedType``1(N.GenericType{System.Int32}.NestedType)")]
[TestCase ("WithNestedGenericType", "WithNestedGenericType``1(N.GenericType{``0}.NestedGenericType{``0}.NestedType)")]
[TestCase ("WithIntOfNestedGenericType", "WithIntOfNestedGenericType``1(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)")]
[TestCase ("WithMultipleTypeParameterAndNestedGenericType", "WithMultipleTypeParameterAndNestedGenericType``2(N.GenericType{``0}.NestedGenericType{``1}.NestedType)")]
[TestCase ("WithMultipleTypeParameterAndIntOfNestedGenericType", "WithMultipleTypeParameterAndIntOfNestedGenericType``2(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)")]
public void GenericMethodWithNestedTypeParameters (string methodName, string docCommentId)
{
var type = GetTestType (typeof (N.GenericMethod));
var method = type.Methods.Single (m => m.Name == methodName);

AssertDocumentID ($"M:N.GenericMethod.{docCommentId}", method);
}

[TestCase ("WithTypeParameterOfGenericMethod", "WithTypeParameterOfGenericMethod``1(System.Collections.Generic.List{``0})")]
[TestCase ("WithTypeParameterOfGenericType", "WithTypeParameterOfGenericType(System.Collections.Generic.Dictionary{`0,`1})")]
[TestCase ("WithTypeParameterOfNestedGenericType", "WithTypeParameterOfNestedGenericType``1(System.Collections.Generic.List{`1})")]
[TestCase ("WithTypeParameterOfGenericTypeAndGenericMethod", "WithTypeParameterOfGenericTypeAndGenericMethod``1(System.Collections.Generic.Dictionary{`1,``0})")]
public void GenericTypeWithTypeParameters (string methodName, string docCommentId)
{
var type = GetTestType (typeof (N.GenericType<>.NestedGenericType<>));
var method = type.Methods.Single (m => m.Name == methodName);

AssertDocumentID ($"M:N.GenericType`1.NestedGenericType`1.{docCommentId}", method);
}

[Test]
public void OpAddition ()
{
Expand Down Expand Up @@ -278,6 +372,11 @@ TypeDefinition GetTestType ()
return typeof (N.X).ToDefinition ();
}

TypeDefinition GetTestType (Type type)
{
return type.ToDefinition ();
}

static void AssertDocumentID (string docId, IMemberDefinition member)
{
Assert.AreEqual (docId, DocCommentId.GetDocCommentId (member));
Expand Down