diff --git a/rocks/Mono.Cecil.Rocks/DocCommentId.cs b/rocks/Mono.Cecil.Rocks/DocCommentId.cs index da74bedee..8af12dd4c 100644 --- a/rocks/Mono.Cecil.Rocks/DocCommentId.cs +++ b/rocks/Mono.Cecil.Rocks/DocCommentId.cs @@ -10,16 +10,18 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace Mono.Cecil.Rocks { - public class DocCommentId - { + public class DocCommentId { + IMemberDefinition commentMember; StringBuilder id; - DocCommentId () + DocCommentId (IMemberDefinition member) { + commentMember = member; id = new StringBuilder (); } @@ -87,54 +89,66 @@ void WriteParameters (IList parameters) void WriteTypeSignature (TypeReference type) { - switch (type.MetadataType) - { - case MetadataType.Array: - WriteArrayTypeSignature ((ArrayType) type); - break; - case MetadataType.ByReference: - WriteTypeSignature (((ByReferenceType) type).ElementType); - id.Append ('@'); - break; - case MetadataType.FunctionPointer: - WriteFunctionPointerTypeSignature ((FunctionPointerType) type); - break; - case MetadataType.GenericInstance: - WriteGenericInstanceTypeSignature ((GenericInstanceType) type); - break; - case MetadataType.Var: + switch (type.MetadataType) { + case MetadataType.Array: + WriteArrayTypeSignature ((ArrayType)type); + break; + case MetadataType.ByReference: + WriteTypeSignature (((ByReferenceType)type).ElementType); + id.Append ('@'); + break; + case MetadataType.FunctionPointer: + WriteFunctionPointerTypeSignature ((FunctionPointerType)type); + break; + case MetadataType.GenericInstance: + WriteGenericInstanceTypeSignature ((GenericInstanceType)type); + break; + case MetadataType.Var: + if (IsGenericMethodTypeParameter (type)) id.Append ('`'); - id.Append (((GenericParameter) type).Position); - break; - case MetadataType.MVar: - id.Append ('`').Append ('`'); - id.Append (((GenericParameter) type).Position); - break; - case MetadataType.OptionalModifier: - WriteModiferTypeSignature ((OptionalModifierType) type, '!'); - break; - case MetadataType.RequiredModifier: - WriteModiferTypeSignature ((RequiredModifierType) type, '|'); - break; - case MetadataType.Pointer: - WriteTypeSignature (((PointerType) type).ElementType); - id.Append ('*'); - break; - default: - WriteTypeFullName (type); - break; + id.Append ('`'); + id.Append (((GenericParameter)type).Position); + break; + case MetadataType.MVar: + id.Append ('`').Append ('`'); + id.Append (((GenericParameter)type).Position); + break; + case MetadataType.OptionalModifier: + WriteModiferTypeSignature ((OptionalModifierType)type, '!'); + break; + case MetadataType.RequiredModifier: + WriteModiferTypeSignature ((RequiredModifierType)type, '|'); + break; + case MetadataType.Pointer: + WriteTypeSignature (((PointerType)type).ElementType); + id.Append ('*'); + break; + default: + WriteTypeFullName (type); + break; } } + 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 (IList list, Action action) @@ -197,10 +211,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 ('.'); } @@ -211,18 +230,69 @@ 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 GetGenericTypeArguments (TypeReference type, GenericTypeOptions options) + { + if (options.IsNestedType) { + var typeParameterCount = type.GenericParameters.Count; + 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; + // var index = type.Name.LastIndexOf ('`'); + // if (index >= 0) + // returnValue = int.Parse (type.Name.Substring (index + 1)); + + // return returnValue; + //} + void WriteItemName (string name) { - id.Append (name.Replace ('.', '#').Replace('<', '{').Replace('>', '}')); + id.Append (name.Replace('.', '#').Replace('<', '{').Replace('>', '}')); } public override string ToString () @@ -235,30 +305,44 @@ public static string GetDocCommentId (IMemberDefinition member) if (member == null) throw new ArgumentNullException ("member"); - var documentId = new DocCommentId (); - - switch (member.MetadataToken.TokenType) - { - case TokenType.Field: - documentId.WriteField ((FieldDefinition) member); - break; - case TokenType.Method: - documentId.WriteMethod ((MethodDefinition) member); - break; - case TokenType.TypeDef: - documentId.WriteType ((TypeDefinition) member); - break; - case TokenType.Event: - documentId.WriteEvent ((EventDefinition) member); - break; - case TokenType.Property: - documentId.WriteProperty ((PropertyDefinition) member); - break; - default: - throw new NotSupportedException (member.FullName); + var documentId = new DocCommentId (member); + + switch (member.MetadataToken.TokenType) { + case TokenType.Field: + documentId.WriteField ((FieldDefinition)member); + break; + case TokenType.Method: + documentId.WriteMethod ((MethodDefinition)member); + break; + case TokenType.TypeDef: + documentId.WriteType ((TypeDefinition)member); + break; + case TokenType.Event: + documentId.WriteEvent ((EventDefinition)member); + break; + case TokenType.Property: + documentId.WriteProperty ((PropertyDefinition)member); + break; + default: + throw new NotSupportedException (member.FullName); } return documentId.ToString (); } + + class GenericTypeOptions { + public bool IsArgument { get; set; } + + public bool IsNestedType { get; set; } + + public IList Arguments { get; set; } + + public int ArgumentIndex { get; set; } + + public static GenericTypeOptions Empty () + { + return new GenericTypeOptions (); + } + } } -} +} \ No newline at end of file diff --git a/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs b/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs index 244d5ae92..8cfc82821 100644 --- a/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs +++ b/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs @@ -6,116 +6,181 @@ using Mono.Cecil.Rocks; -namespace N -{ +namespace N { - /// - /// ID string generated is "T:N.X". - /// - public class X : IX> - { - /// - /// ID string generated is "M:N.X.#ctor". - /// - public X() { } + /// + /// ID string generated is "T:N.X". + /// + public class X : IX> { + /// + /// ID string generated is "M:N.X.#ctor". + /// + public X () { } - /// - /// ID string generated is "M:N.X.#ctor(System.Int32)". - /// - /// Describe parameter. - public X(int i) { } + /// + /// ID string generated is "M:N.X.#ctor(System.Int32)". + /// + /// Describe parameter. + public X (int i) { } - /// - /// ID string generated is "F:N.X.q". - /// - public string q; + /// + /// ID string generated is "F:N.X.q". + /// + public string q; - /// - /// ID string generated is "F:N.X.PI". - /// - public const double PI = 3.14; + /// + /// ID string generated is "F:N.X.PI". + /// + public const double PI = 3.14; - /// - /// ID string generated is "M:N.X.f". - /// - public int f() { return 1; } + /// + /// ID string generated is "M:N.X.f". + /// + public int f () { return 1; } - /// - /// ID string generated is "M:N.X.bb(System.String,System.Int32@)". - /// - public int bb(string s, ref int y) { return 1; } + /// + /// ID string generated is "M:N.X.bb(System.String,System.Int32@)". + /// + public int bb (string s, ref int y) { return 1; } - /// - /// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])". - /// - public int gg(short[] array1, int[,] array) { return 0; } + /// + /// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])". + /// + public int gg (short [] array1, int [,] array) { return 0; } - /// - /// ID string generated is "M:N.X.op_Addition(N.X,N.X)". - /// - public static X operator +(X x, X xx) { return x; } + /// + /// ID string generated is "M:N.X.op_Addition(N.X,N.X)". + /// + public static X operator + (X x, X xx) { return x; } - /// - /// ID string generated is "P:N.X.prop". - /// - public int prop { get { return 1; } set { } } + /// + /// ID string generated is "P:N.X.prop". + /// + public int prop { get { return 1; } set { } } - /// - /// ID string generated is "E:N.X.d". - /// + /// + /// ID string generated is "E:N.X.d". + /// #pragma warning disable 67 - public event D d; + public event D d; #pragma warning restore 67 /// /// ID string generated is "P:N.X.Item(System.String)". /// - public int this[string s] { get { return 1; } } + public int this [string s] { get { return 1; } } - /// - /// ID string generated is "T:N.X.Nested". - /// - public class Nested { } + /// + /// ID string generated is "T:N.X.Nested". + /// + public class Nested { } - /// - /// ID string generated is "T:N.X.D". - /// - public delegate void D(int i); + /// + /// ID string generated is "T:N.X.D". + /// + public delegate void D (int i); - /// - /// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32". - /// - public static explicit operator int(X x) { return 1; } + /// + /// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32". + /// + public static explicit operator int (X x) { return 1; } - public static void Linq (IEnumerable enumerable, Func selector) - { - } + public static void Linq (IEnumerable enumerable, Func selector) + { + } - /// + /// /// ID string generated is "M:N.X.N#IX{N#KVP{System#String,System#Int32}}#IXA(N.KVP{System.String,System.Int32})" - /// - void IX>.IXA (KVP k) { } - } + /// + void IX>.IXA (KVP k) { } + } - public interface IX - { + public interface IX { void IXA (K k); } public class KVP { } + + public class GenericMethod { + /// + /// ID string generated is "M:N.GenericMethod.WithNestedType``1(N.GenericType{``0}.NestedType)". + /// + public void WithNestedType (GenericType.NestedType nestedType) { } + + + /// + /// ID string generated is "M:N.GenericMethod.WithIntOfNestedType``1(N.GenericType{System.Int32}.NestedType)". + /// + public void WithIntOfNestedType (GenericType.NestedType nestedType) { } + + + /// + /// ID string generated is "M:N.GenericMethod.WithNestedGenericType``1(N.GenericType{``0}.NestedGenericType{``0}.NestedType)". + /// + public void WithNestedGenericType (GenericType.NestedGenericType.NestedType nestedType) { } + + + /// + /// ID string generated is "M:N.GenericMethod.WithIntOfNestedGenericType``1(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)". + /// + public void WithIntOfNestedGenericType (GenericType.NestedGenericType.NestedType nestedType) { } + + + /// + /// ID string generated is "M:N.GenericMethod.WithMultipleTypeParameterAndNestedGenericType``2(N.GenericType{``0}.NestedGenericType{``1}.NestedType)". + /// + public void WithMultipleTypeParameterAndNestedGenericType (GenericType.NestedGenericType.NestedType nestedType) { } + + + /// + /// ID string generated is "M:N.GenericMethod.WithMultipleTypeParameterAndIntOfNestedGenericType``2(N.GenericType{System.Int32}.NestedGenericType{System.Int32}.NestedType)". + /// + public void WithMultipleTypeParameterAndIntOfNestedGenericType (GenericType.NestedGenericType.NestedType nestedType) { } + } + + public class GenericType { + public class NestedType { } + + public class NestedGenericType { + public class NestedType { } + + /// + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericMethod``1(System.Collections.Generic.List{``0})" + /// + public void WithTypeParameterOfGenericMethod (List list) { } + + + /// + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericType(System.Collections.Generic.Dictionary{`0,`1})" + /// + public void WithTypeParameterOfGenericType (Dictionary dict) { } + + + /// + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericType``1(System.Collections.Generic.List{`1})" + /// + public void WithTypeParameterOfNestedGenericType (List list) { } + + + /// + /// ID string generated is "M:N.GenericType`1.NestedGenericType`1.WithTypeParameterOfGenericTypeAndGenericMethod``1(System.Collections.Generic.Dictionary{`1,``0})" + /// + public void WithTypeParameterOfGenericTypeAndGenericMethod (Dictionary dict) { } + } + } } namespace Mono.Cecil.Tests { @@ -182,7 +247,7 @@ public void MethodWithByRefParameters () AssertDocumentID ("M:N.X.bb(System.String,System.Int32@)", method); } - + [Test] public void MethodWithArrayParameters () { @@ -192,6 +257,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 () { @@ -268,7 +359,7 @@ public void Linq () public void EII () { var type = GetTestType (); - var method = type.Methods.Where (m => m.Name.Contains("IXA")).First (); + var method = type.Methods.Where (m => m.Name.Contains ("IXA")).First (); AssertDocumentID ("M:N.X.N#IX{N#KVP{System#String,System#Int32}}#IXA(N.KVP{System.String,System.Int32})", method); } @@ -278,9 +369,14 @@ 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)); } } -} +} \ No newline at end of file