You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
14 KiB
433 lines
14 KiB
// Copyright (c) 2010-2018 AlphaSierraPapa for the SharpDevelop Team
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
using ICSharpCode.Decompiler.TypeSystem.Implementation;
|
|
|
|
namespace ICSharpCode.Decompiler.Documentation
|
|
{
|
|
/// <summary>
|
|
/// Provides ID strings for entities. (C# 4.0 spec, §A.3.1)
|
|
/// ID strings are used to identify members in XML documentation files.
|
|
/// </summary>
|
|
public static class IdStringProvider
|
|
{
|
|
#region GetIdString
|
|
/// <summary>
|
|
/// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity.
|
|
/// </summary>
|
|
public static string GetIdString(this IEntity entity)
|
|
{
|
|
StringBuilder b = new StringBuilder();
|
|
switch (entity.SymbolKind)
|
|
{
|
|
case SymbolKind.TypeDefinition:
|
|
b.Append("T:");
|
|
AppendTypeName(b, (ITypeDefinition)entity, false);
|
|
return b.ToString();
|
|
case SymbolKind.Field:
|
|
b.Append("F:");
|
|
break;
|
|
case SymbolKind.Property:
|
|
case SymbolKind.Indexer:
|
|
b.Append("P:");
|
|
break;
|
|
case SymbolKind.Event:
|
|
b.Append("E:");
|
|
break;
|
|
default:
|
|
b.Append("M:");
|
|
break;
|
|
}
|
|
IMember member = (IMember)entity;
|
|
if (member.DeclaringType != null)
|
|
{
|
|
AppendTypeName(b, member.DeclaringType, false);
|
|
b.Append('.');
|
|
}
|
|
if (member.IsExplicitInterfaceImplementation && member.Name.IndexOf('.') < 0 && member.ExplicitlyImplementedInterfaceMembers.Count() == 1)
|
|
{
|
|
AppendTypeName(b, member.ExplicitlyImplementedInterfaceMembers.First().DeclaringType, true);
|
|
b.Append('#');
|
|
}
|
|
b.Append(member.Name.Replace('.', '#').Replace('<', '{').Replace('>', '}'));
|
|
IMethod method = member as IMethod;
|
|
if (method != null && method.TypeParameters.Count > 0)
|
|
{
|
|
b.Append("``");
|
|
b.Append(method.TypeParameters.Count);
|
|
}
|
|
IParameterizedMember parameterizedMember = member as IParameterizedMember;
|
|
if (parameterizedMember != null && parameterizedMember.Parameters.Count > 0)
|
|
{
|
|
b.Append('(');
|
|
var parameters = parameterizedMember.Parameters;
|
|
for (int i = 0; i < parameters.Count; i++)
|
|
{
|
|
if (i > 0)
|
|
b.Append(',');
|
|
AppendTypeName(b, parameters[i].Type, false);
|
|
}
|
|
b.Append(')');
|
|
}
|
|
if (member.SymbolKind == SymbolKind.Operator && (member.Name == "op_Implicit" || member.Name == "op_Explicit"))
|
|
{
|
|
b.Append('~');
|
|
AppendTypeName(b, member.ReturnType, false);
|
|
}
|
|
return b.ToString();
|
|
}
|
|
#endregion
|
|
|
|
#region GetTypeName
|
|
public static string GetTypeName(IType type)
|
|
{
|
|
if (type == null)
|
|
throw new ArgumentNullException(nameof(type));
|
|
StringBuilder b = new StringBuilder();
|
|
AppendTypeName(b, type, false);
|
|
return b.ToString();
|
|
}
|
|
|
|
static void AppendTypeName(StringBuilder b, IType type, bool explicitInterfaceImpl)
|
|
{
|
|
switch (type.Kind)
|
|
{
|
|
case TypeKind.Dynamic:
|
|
b.Append(explicitInterfaceImpl ? "System#Object" : "System.Object");
|
|
break;
|
|
case TypeKind.TypeParameter:
|
|
ITypeParameter tp = (ITypeParameter)type;
|
|
if (explicitInterfaceImpl)
|
|
{
|
|
b.Append(tp.Name);
|
|
}
|
|
else
|
|
{
|
|
b.Append('`');
|
|
if (tp.OwnerType == SymbolKind.Method)
|
|
b.Append('`');
|
|
b.Append(tp.Index);
|
|
}
|
|
break;
|
|
case TypeKind.Array:
|
|
ArrayType array = (ArrayType)type;
|
|
AppendTypeName(b, array.ElementType, explicitInterfaceImpl);
|
|
b.Append('[');
|
|
if (array.Dimensions > 1)
|
|
{
|
|
for (int i = 0; i < array.Dimensions; i++)
|
|
{
|
|
if (i > 0)
|
|
b.Append(explicitInterfaceImpl ? '@' : ',');
|
|
if (!explicitInterfaceImpl)
|
|
b.Append("0:");
|
|
}
|
|
}
|
|
b.Append(']');
|
|
break;
|
|
case TypeKind.Pointer:
|
|
AppendTypeName(b, ((PointerType)type).ElementType, explicitInterfaceImpl);
|
|
b.Append('*');
|
|
break;
|
|
case TypeKind.ByReference:
|
|
AppendTypeName(b, ((ByReferenceType)type).ElementType, explicitInterfaceImpl);
|
|
b.Append('@');
|
|
break;
|
|
default:
|
|
IType declType = type.DeclaringType;
|
|
if (declType != null)
|
|
{
|
|
AppendTypeName(b, declType, explicitInterfaceImpl);
|
|
b.Append(explicitInterfaceImpl ? '#' : '.');
|
|
b.Append(type.Name);
|
|
AppendTypeParameters(b, type, declType.TypeParameterCount, explicitInterfaceImpl);
|
|
}
|
|
else
|
|
{
|
|
if (explicitInterfaceImpl)
|
|
b.Append(type.FullName.Replace('.', '#'));
|
|
else
|
|
b.Append(type.FullName);
|
|
AppendTypeParameters(b, type, 0, explicitInterfaceImpl);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void AppendTypeParameters(StringBuilder b, IType type, int outerTypeParameterCount, bool explicitInterfaceImpl)
|
|
{
|
|
int tpc = type.TypeParameterCount - outerTypeParameterCount;
|
|
if (tpc > 0)
|
|
{
|
|
ParameterizedType pt = type as ParameterizedType;
|
|
if (pt != null)
|
|
{
|
|
b.Append('{');
|
|
var ta = pt.TypeArguments;
|
|
for (int i = outerTypeParameterCount; i < ta.Count; i++)
|
|
{
|
|
if (i > outerTypeParameterCount)
|
|
b.Append(explicitInterfaceImpl ? '@' : ',');
|
|
AppendTypeName(b, ta[i], explicitInterfaceImpl);
|
|
}
|
|
b.Append('}');
|
|
}
|
|
else
|
|
{
|
|
b.Append('`');
|
|
b.Append(tpc);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ParseMemberName
|
|
/// <summary>
|
|
/// Parse the ID string into a member reference.
|
|
/// </summary>
|
|
/// <param name="memberIdString">The ID string representing the member (with "M:", "F:", "P:" or "E:" prefix).</param>
|
|
/// <returns>A member reference that represents the ID string.</returns>
|
|
/// <exception cref="ReflectionNameParseException">The syntax of the ID string is invalid</exception>
|
|
/// <remarks>
|
|
/// The member reference will look in <see cref="ITypeResolveContext.CurrentModule"/> first,
|
|
/// and if the member is not found there,
|
|
/// it will look in all other assemblies of the compilation.
|
|
/// </remarks>
|
|
public static IMemberReference ParseMemberIdString(string memberIdString)
|
|
{
|
|
if (memberIdString == null)
|
|
throw new ArgumentNullException(nameof(memberIdString));
|
|
if (memberIdString.Length < 2 || memberIdString[1] != ':')
|
|
throw new ReflectionNameParseException(0, "Missing type tag");
|
|
char typeChar = memberIdString[0];
|
|
int parenPos = memberIdString.IndexOf('(');
|
|
if (parenPos < 0)
|
|
parenPos = memberIdString.LastIndexOf('~');
|
|
if (parenPos < 0)
|
|
parenPos = memberIdString.Length;
|
|
int dotPos = memberIdString.LastIndexOf('.', parenPos - 1);
|
|
if (dotPos < 0)
|
|
throw new ReflectionNameParseException(0, "Could not find '.' separating type name from member name");
|
|
string typeName = memberIdString.Substring(0, dotPos);
|
|
int pos = 2;
|
|
ITypeReference typeReference = ParseTypeName(typeName, ref pos);
|
|
if (pos != typeName.Length)
|
|
throw new ReflectionNameParseException(pos, "Expected end of type name");
|
|
// string memberName = memberIDString.Substring(dotPos + 1, parenPos - (dotPos + 1));
|
|
// pos = memberName.LastIndexOf("``");
|
|
// if (pos > 0)
|
|
// memberName = memberName.Substring(0, pos);
|
|
// memberName = memberName.Replace('#', '.');
|
|
return new IdStringMemberReference(typeReference, typeChar, memberIdString);
|
|
}
|
|
#endregion
|
|
|
|
#region ParseTypeName
|
|
/// <summary>
|
|
/// Parse the ID string type name into a type reference.
|
|
/// </summary>
|
|
/// <param name="typeName">The ID string representing the type (the "T:" prefix is optional).</param>
|
|
/// <returns>A type reference that represents the ID string.</returns>
|
|
/// <exception cref="ReflectionNameParseException">The syntax of the ID string is invalid</exception>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The type reference will look in <see cref="ITypeResolveContext.CurrentModule"/> first,
|
|
/// and if the type is not found there,
|
|
/// it will look in all other assemblies of the compilation.
|
|
/// </para>
|
|
/// <para>
|
|
/// If the type is open (contains type parameters '`0' or '``0'),
|
|
/// an <see cref="ITypeResolveContext"/> with the appropriate CurrentTypeDefinition/CurrentMember is required
|
|
/// to resolve the reference to the ITypeParameter.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static ITypeReference ParseTypeName(string typeName)
|
|
{
|
|
if (typeName == null)
|
|
throw new ArgumentNullException(nameof(typeName));
|
|
int pos = 0;
|
|
if (typeName.StartsWith("T:", StringComparison.Ordinal))
|
|
pos = 2;
|
|
ITypeReference r = ParseTypeName(typeName, ref pos);
|
|
if (pos < typeName.Length)
|
|
throw new ReflectionNameParseException(pos, "Expected end of type name");
|
|
return r;
|
|
}
|
|
|
|
static bool IsIDStringSpecialCharacter(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case ':':
|
|
case '{':
|
|
case '}':
|
|
case '[':
|
|
case ']':
|
|
case '(':
|
|
case ')':
|
|
case '`':
|
|
case '*':
|
|
case '@':
|
|
case ',':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static ITypeReference ParseTypeName(string typeName, ref int pos)
|
|
{
|
|
string reflectionTypeName = typeName;
|
|
if (pos == typeName.Length)
|
|
throw new ReflectionNameParseException(pos, "Unexpected end");
|
|
ITypeReference result;
|
|
if (reflectionTypeName[pos] == '`')
|
|
{
|
|
// type parameter reference
|
|
pos++;
|
|
if (pos == reflectionTypeName.Length)
|
|
throw new ReflectionNameParseException(pos, "Unexpected end");
|
|
if (reflectionTypeName[pos] == '`')
|
|
{
|
|
// method type parameter reference
|
|
pos++;
|
|
int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
|
|
result = TypeParameterReference.Create(SymbolKind.Method, index);
|
|
}
|
|
else
|
|
{
|
|
// class type parameter reference
|
|
int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
|
|
result = TypeParameterReference.Create(SymbolKind.TypeDefinition, index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a type parameter reference: read the actual type name
|
|
List<ITypeReference> typeArguments = new List<ITypeReference>();
|
|
string typeNameWithoutSuffix = ReadTypeName(typeName, ref pos, true, out int typeParameterCount, typeArguments);
|
|
result = new GetPotentiallyNestedClassTypeReference(typeNameWithoutSuffix, typeParameterCount);
|
|
while (pos < typeName.Length && typeName[pos] == '.')
|
|
{
|
|
pos++;
|
|
string nestedTypeName = ReadTypeName(typeName, ref pos, false, out typeParameterCount, typeArguments);
|
|
result = new NestedTypeReference(result, nestedTypeName, typeParameterCount);
|
|
}
|
|
if (typeArguments.Count > 0)
|
|
{
|
|
result = new ParameterizedTypeReference(result, typeArguments);
|
|
}
|
|
}
|
|
while (pos < typeName.Length)
|
|
{
|
|
switch (typeName[pos])
|
|
{
|
|
case '[':
|
|
int dimensions = 1;
|
|
do
|
|
{
|
|
pos++;
|
|
if (pos == typeName.Length)
|
|
throw new ReflectionNameParseException(pos, "Unexpected end");
|
|
if (typeName[pos] == ',')
|
|
dimensions++;
|
|
} while (typeName[pos] != ']');
|
|
result = new ArrayTypeReference(result, dimensions);
|
|
break;
|
|
case '*':
|
|
result = new PointerTypeReference(result);
|
|
break;
|
|
case '@':
|
|
result = new ByReferenceTypeReference(result);
|
|
break;
|
|
default:
|
|
return result;
|
|
}
|
|
pos++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static string ReadTypeName(string typeName, ref int pos, bool allowDottedName, out int typeParameterCount, List<ITypeReference> typeArguments)
|
|
{
|
|
int startPos = pos;
|
|
// skip the simple name portion:
|
|
while (pos < typeName.Length && !IsIDStringSpecialCharacter(typeName[pos]) && (allowDottedName || typeName[pos] != '.'))
|
|
pos++;
|
|
if (pos == startPos)
|
|
throw new ReflectionNameParseException(pos, "Expected type name");
|
|
string shortTypeName = typeName.Substring(startPos, pos - startPos);
|
|
// read type arguments:
|
|
typeParameterCount = 0;
|
|
if (pos < typeName.Length && typeName[pos] == '`')
|
|
{
|
|
// unbound generic type
|
|
pos++;
|
|
typeParameterCount = ReflectionHelper.ReadTypeParameterCount(typeName, ref pos);
|
|
}
|
|
else if (pos < typeName.Length && typeName[pos] == '{')
|
|
{
|
|
// bound generic type
|
|
do
|
|
{
|
|
pos++;
|
|
typeArguments.Add(ParseTypeName(typeName, ref pos));
|
|
typeParameterCount++;
|
|
if (pos == typeName.Length)
|
|
throw new ReflectionNameParseException(pos, "Unexpected end");
|
|
} while (typeName[pos] == ',');
|
|
if (typeName[pos] != '}')
|
|
throw new ReflectionNameParseException(pos, "Expected '}'");
|
|
pos++;
|
|
}
|
|
return shortTypeName;
|
|
}
|
|
#endregion
|
|
|
|
#region FindEntity
|
|
/// <summary>
|
|
/// Finds the entity in the given type resolve context.
|
|
/// </summary>
|
|
/// <param name="idString">ID string of the entity.</param>
|
|
/// <param name="context">Type resolve context</param>
|
|
/// <returns>Returns the entity, or null if it is not found.</returns>
|
|
/// <exception cref="ReflectionNameParseException">The syntax of the ID string is invalid</exception>
|
|
public static IEntity FindEntity(string idString, ITypeResolveContext context)
|
|
{
|
|
if (idString == null)
|
|
throw new ArgumentNullException(nameof(idString));
|
|
if (context == null)
|
|
throw new ArgumentNullException(nameof(context));
|
|
if (idString.StartsWith("T:", StringComparison.Ordinal))
|
|
{
|
|
return ParseTypeName(idString.Substring(2)).Resolve(context).GetDefinition();
|
|
}
|
|
else
|
|
{
|
|
return ParseMemberIdString(idString).Resolve(context);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|