// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System.Collections;
using System.Collections.Generic;
using Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;
namespace Nova.CodeDOM
{
/// <summary>
/// Represents a type parameter of a generic type or method declaration.
/// </summary>
public class TypeParameter : CodeObject, ITypeDecl
{
#region /* FIELDS */
protected string _name;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a <see cref="TypeParameter"/> with the specified name.
/// </summary>
public TypeParameter(string name)
{
_name = name;
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The name of the <see cref="TypeParameter"/>.
/// </summary>
public string Name
{
get { return _name; }
set { _name = value; }
}
/// <summary>
/// The descriptive category of the code object.
/// </summary>
public string Category
{
get { return "type parameter"; }
}
/// <summary>
/// Always <c>0</c>.
/// </summary>
public int TypeParameterCount
{
get { return 0; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsAbstract
{
get { return false; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsClass
{
get { return false; }
}
/// <summary>
/// True if the <see cref="TypeParameter"/> has a delegate type.
/// </summary>
public bool IsDelegateType
{
get { return GetTypeConstraint().IsDelegateType; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsEnum
{
get { return false; }
}
/// <summary>
/// Always <c>true</c>.
/// </summary>
public bool IsGenericParameter
{
get { return true; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsGenericType
{
get { return false; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsInterface
{
get { return false; }
}
/// <summary>
/// Always <c>true</c>.
/// </summary>
public bool IsNested
{
// TypeParameters are considered nested types (including in .NET reflection)
get { return true; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsNullableType
{
get { return false; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsPartial
{
get { return false; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsStruct
{
get { return false; }
}
/// <summary>
/// Always <c>false</c>.
/// </summary>
public bool IsValueType
{
get { return false; }
}
#endregion
#region /* METHODS */
/// <summary>
/// Create a reference to the <see cref="TypeParameter"/>.
/// </summary>
/// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public override SymbolicRef CreateRef(bool isFirstOnLine)
{
return new TypeParameterRef(this, isFirstOnLine);
}
/// <summary>
/// Create a reference to the <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public TypeRef CreateRef(bool isFirstOnLine, ChildList<Expression> typeArguments, List<int> arrayRanks)
{
// Ignore any type arguments - a TypeParameterRef can't have any
return new TypeParameterRef(this, isFirstOnLine, arrayRanks);
}
/// <summary>
/// Create a reference to the <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public TypeRef CreateRef(bool isFirstOnLine, ChildList<Expression> typeArguments)
{
// Ignore any type arguments - a TypeParameterRef can't have any
return new TypeParameterRef(this, isFirstOnLine);
}
/// <summary>
/// Create a reference to the <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public TypeRef CreateRef(ChildList<Expression> typeArguments, List<int> arrayRanks)
{
// Ignore any type arguments - a TypeParameterRef can't have any
return new TypeParameterRef(this, false, arrayRanks);
}
/// <summary>
/// Create a reference to the <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public TypeRef CreateRef(ChildList<Expression> typeArguments)
{
// Ignore any type arguments - a TypeParameterRef can't have any
return new TypeParameterRef(this, false);
}
/// <summary>
/// Create an array reference to this <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public TypeRef CreateArrayRef(bool isFirstOnLine, params int[] ranks)
{
return new TypeParameterRef(this, isFirstOnLine, ranks);
}
/// <summary>
/// Create an array reference to this <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeParameterRef"/>.</returns>
public TypeRef CreateArrayRef(params int[] ranks)
{
return new TypeParameterRef(this, false, ranks);
}
/// <summary>
/// Create a nullable reference to this <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeRef"/>.</returns>
public TypeRef CreateNullableRef(bool isFirstOnLine)
{
return TypeRef.CreateNullable(CreateRef(), isFirstOnLine);
}
/// <summary>
/// Create a nullable reference to this <see cref="TypeParameter"/>.
/// </summary>
/// <returns>A <see cref="TypeRef"/>.</returns>
public TypeRef CreateNullableRef()
{
return TypeRef.CreateNullable(CreateRef());
}
/// <summary>
/// Get any constraints for this <see cref="TypeParameter"/> from the parent type or generic method.
/// </summary>
public List<TypeParameterConstraint> GetConstraints()
{
return ((ITypeParameters)_parent).GetTypeParameterConstraints(this);
}
/// <summary>
/// Get the main constraining type (if any - not including interfaces).
/// </summary>
public TypeRef GetTypeConstraint()
{
List<TypeParameterConstraint> constraints = GetConstraints();
if (constraints != null)
{
foreach (TypeParameterConstraint constraint in constraints)
{
if (constraint is TypeConstraint)
{
// Ignore if an UnresolvedRef, because we can't distinguish class vs interface
TypeRef typeRef = ((TypeConstraint)constraint).EvaluateType() as TypeRef;
if (typeRef != null && typeRef.IsClass)
return typeRef;
}
else if (constraint is ClassConstraint)
return TypeRef.ObjectRef;
else if (constraint is StructConstraint)
return TypeRef.ValueTypeRef;
}
}
return TypeRef.ObjectRef;
}
/// <summary>
/// Get the base type - always a <see cref="TypeRef"/> to the 'object' type.
/// </summary>
public TypeRef GetBaseType()
{
return TypeRef.ObjectRef;
}
/// <summary>
/// Always returns <c>null</c>.
/// </summary>
public ConstructorRef GetConstructor(params TypeRefBase[] parameterTypes)
{
return null;
}
/// <summary>
/// Get all non-static constructors for this type.
/// </summary>
public NamedCodeObjectGroup GetConstructors(bool currentPartOnly)
{
return null;
}
/// <summary>
/// Get all non-static constructors for this type.
/// </summary>
public NamedCodeObjectGroup GetConstructors()
{
return null;
}
/// <summary>
/// Always returns <c>null</c>.
/// </summary>
public MethodRef GetMethod(string name, params TypeRefBase[] parameterTypes)
{
return null;
}
/// <summary>
/// Does nothing.
/// </summary>
public void GetMethods(string name, bool searchBaseClasses, NamedCodeObjectGroup results)
{ }
/// <summary>
/// Always returns <c>null</c> for this type.
/// </summary>
/// <param name="name">The method name.</param>
/// <param name="searchBaseClasses">Pass <c>false</c> to NOT search base classes.</param>
public List<MethodRef> GetMethods(string name, bool searchBaseClasses)
{
return null;
}
/// <summary>
/// Always returns <c>null</c> for this type.
/// </summary>
/// <param name="name">The method name.</param>
public List<MethodRef> GetMethods(string name)
{
return null;
}
/// <summary>
/// Always returns <c>null</c>.
/// </summary>
public PropertyRef GetProperty(string name)
{
return null;
}
/// <summary>
/// Always returns <c>null</c>.
/// </summary>
public FieldRef GetField(string name)
{
return null;
}
/// <summary>
/// Always returns <c>null</c>.
/// </summary>
public TypeRef GetNestedType(string name)
{
return null;
}
/// <summary>
/// Get the delegate parameters of the constraining type (if any).
/// </summary>
public ICollection GetDelegateParameters()
{
return GetTypeConstraint().GetDelegateParameters();
}
/// <summary>
/// Get the delegate return type of the constraining type (if any).
/// </summary>
public TypeRefBase GetDelegateReturnType()
{
return GetTypeConstraint().GetDelegateReturnType();
}
/// <summary>
/// Determine if the constraining type is assignable from the specified type.
/// </summary>
public bool IsAssignableFrom(TypeRef typeRef)
{
return GetTypeConstraint().IsAssignableFrom(typeRef);
}
/// <summary>
/// Determine if the constraining type is a subclass of the specified type.
/// </summary>
public bool IsSubclassOf(TypeRef classTypeRef)
{
return GetTypeConstraint().IsSubclassOf(classTypeRef);
}
/// <summary>
/// Determine if the constraining type implements the specified interface type.
/// </summary>
public bool IsImplementationOf(TypeRef interfaceTypeRef)
{
return GetTypeConstraint().IsImplementationOf(interfaceTypeRef);
}
/// <summary>
/// Add the <see cref="CodeObject"/> to the specified dictionary.
/// </summary>
public virtual void AddToDictionary(NamedCodeObjectDictionary dictionary)
{
dictionary.Add(Name, this);
}
/// <summary>
/// Remove the <see cref="CodeObject"/> from the specified dictionary.
/// </summary>
public virtual void RemoveFromDictionary(NamedCodeObjectDictionary dictionary)
{
dictionary.Remove(Name, this);
}
/// <summary>
/// Get the full name of the <see cref="INamedCodeObject"/>, including any namespace name.
/// </summary>
/// <param name="descriptive">True to display type parameters and method parameters, otherwise false.</param>
public string GetFullName(bool descriptive)
{
if (Parent is TypeDecl)
return ((TypeDecl)Parent).GetFullName(descriptive) + "." + _name;
if (Parent is GenericMethodDecl)
return ((GenericMethodDecl)Parent).GetFullName(descriptive) + "." + _name;
return _name;
}
/// <summary>
/// Get the full name of the <see cref="INamedCodeObject"/>, including any namespace name.
/// </summary>
public string GetFullName()
{
return GetFullName(false);
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the start of a list of type parameters.
/// </summary>
public const string ParseTokenStart = TypeRefBase.ParseTokenArgumentStart;
/// <summary>
/// The token used to parse the end of a list of type parameters.
/// </summary>
public const string ParseTokenEnd = TypeRefBase.ParseTokenArgumentEnd;
/// <summary>
/// The token used to parse between type parameters.
/// </summary>
public const string ParseTokenSeparator = ParameterDecl.ParseTokenSeparator;
/// <summary>
/// The token used to parse an 'out' type parameter.
/// </summary>
public const string ParseTokenOut = "out";
/// <summary>
/// The token used to parse an 'in' type parameter.
/// </summary>
public const string ParseTokenIn = "in";
// Alternate type argument delimiters allowed for code embedded inside documentation comments.
// The C# style delimiters are also allowed in doc comments, although they shouldn't show up
// usually, since they cause errors with parsing the XML properly - but they could be used
// programmatically in certain situations. Both styles are thus supported inside doc comments,
// but the open and close delimiters must match for each pair. Note that in the case of a
// type declaration, the alternate type argument delimiters are ambiguous with block delimiters -
// this is handled by looking for a type argument pattern.
/// <summary>
/// The alternate token used to parse the start of a list of type parameters inside a documentation comment.
/// </summary>
public const string ParseTokenAltStart = TypeRefBase.ParseTokenAltArgumentStart;
/// <summary>
/// The alternate token used to parse the end of a list of type parameters inside a documentation comment.
/// </summary>
public const string ParseTokenAltEnd = TypeRefBase.ParseTokenAltArgumentEnd;
protected TypeParameter(Parser parser, CodeObject parent)
: base(parser, parent)
{
// If we're starting with an attribute, ignore any newline parsed in the base constructor
if (parser.TokenText == Attribute.ParseTokenStart)
IsFirstOnLine = false;
Attribute.ParseAttributes(parser, this); // Parse any attributes
_name = parser.GetIdentifierText(); // Parse the name
}
/// <summary>
/// Parse a list of type parameters.
/// </summary>
public static ChildList<TypeParameter> ParseList(Parser parser, CodeObject parent)
{
ChildList<TypeParameter> parameters = null;
if (parser.TokenText == ParseTokenStart || (parser.InDocComment && parser.TokenText == ParseTokenAltStart
&& TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, ParseFlags.None)))
{
string argumentEnd = (parser.TokenText == ParseTokenAltStart ? ParseTokenAltEnd : ParseTokenEnd);
parent.MoveAllComments(parser.LastToken); // Move any skipped comments to the parent
parser.NextToken(); // Move past '<'
// Create a string of possible terminators (assuming 1 char terminators for now)
string terminators = argumentEnd + MethodDeclBase.ParseTokenStart + ConstraintClause.ParseTokenSeparator + Statement.ParseTokenTerminator;
while (parser.Token != null && (parser.TokenText.Length != 1 || terminators.IndexOf(parser.TokenText[0]) < 0))
{
TypeParameter typeParameter = new TypeParameter(parser, parent);
if (typeParameter.Name != null)
{
if (parameters == null)
parameters = new ChildList<TypeParameter>(parent);
parameters.Add(typeParameter);
if (parser.TokenText == ParseTokenSeparator)
parser.NextToken(); // Move past ','
}
else
parser.NextToken(); // Move past bad token (non-identifier)
}
parser.NextToken(); // Move past '>'
}
return parameters;
}
#endregion
#region /* RESOLVING */
/// <summary>
/// Resolve all child symbolic references, using the specified <see cref="ResolveCategory"/> and <see cref="ResolveFlags"/>.
/// </summary>
public override CodeObject Resolve(ResolveCategory resolveCategory, ResolveFlags flags)
{
if ((flags & (ResolveFlags.Phase1 | ResolveFlags.Phase2)) == 0)
ResolveAttributes(flags);
return this;
}
/// <summary>
/// Resolve child code objects that match the specified name.
/// </summary>
public void ResolveRef(string name, Resolver resolver)
{
// Find the member on any constraining type(s)
List<TypeParameterConstraint> constraints = GetConstraints();
if (constraints != null)
{
foreach (TypeParameterConstraint constraint in constraints)
{
if (constraint is TypeConstraint)
{
// Don't look in the constraining type if it's an interface and we already have a complete match
// (we might have multiple interface constraints, and one might have a base interface which is
// also specified directly as a separate constraint, causing duplicate matches).
TypeRef typeRef = ((TypeConstraint)constraint).EvaluateType() as TypeRef;
if (typeRef != null && !(typeRef.IsInterface && resolver.HasCompleteMatch))
typeRef.ResolveRef(name, resolver);
}
}
}
// Default to searching the 'object' type
if (!resolver.HasCompleteMatch)
TypeRef.ResolveRef(TypeRef.ObjectRef, name, resolver);
}
/// <summary>
/// Resolve indexers.
/// </summary>
public void ResolveIndexerRef(Resolver resolver)
{
// Find the member on any constraining type(s)
List<TypeParameterConstraint> constraints = GetConstraints();
if (constraints != null)
{
foreach (TypeParameterConstraint constraint in constraints)
{
if (constraint is TypeConstraint)
{
// Don't look in the constraining type if it's an interface and we already have a complete match
// (we might have multiple interface constraints, and one might have a base interface which is
// also specified directly as a separate constraint, causing duplicate matches).
TypeRef typeRef = ((TypeConstraint)constraint).EvaluateType() as TypeRef;
if (typeRef != null && !(typeRef.IsInterface && resolver.HasCompleteMatch))
typeRef.ResolveIndexerRef(resolver);
}
}
}
// Default to searching the 'object' type (even though it doesn't normally have any indexers, this
// is possible for alternate core libs, and this has been necessary for some users of this library).
if (!resolver.HasCompleteMatch)
TypeRef.ObjectRef.ResolveIndexerRef(resolver);
}
/// <summary>
/// Find a type argument in any associated constraints for the specified type parameter.
/// </summary>
public TypeRefBase FindTypeArgument(TypeParameterRef typeParameterRef)
{
TypeRefBase typeRefBase = null;
List<TypeParameterConstraint> constraints = GetConstraints();
if (constraints != null)
{
foreach (TypeParameterConstraint constraint in constraints)
{
if (constraint is TypeConstraint)
{
typeRefBase = ((TypeConstraint)constraint).Type.FindTypeArgument(typeParameterRef);
if (typeRefBase != null)
break;
}
}
}
return typeRefBase;
}
/// <summary>
/// Find a type argument in a base class for the specified type parameter.
/// </summary>
public TypeRefBase FindTypeArgumentInBase(TypeParameterRef typeParameterRef)
{
return null;
}
#endregion
#region /* FORMATTING */
/// <summary>
/// True if the code object defaults to starting on a new line.
/// </summary>
public override bool IsFirstOnLineDefault
{
get { return false; }
}
/// <summary>
/// The number of newlines preceeding the object (0 to N).
/// </summary>
public override int NewLines
{
get { return base.NewLines; }
set
{
// If we're changing to zero, also change all prefix attributes to zero
bool isFirstOnLine = (value != 0);
if (_annotations != null && !isFirstOnLine && IsFirstOnLine)
{
foreach (Annotation annotation in _annotations)
{
if (annotation is Attribute)
annotation.IsFirstOnLine = false;
}
}
base.NewLines = value;
}
}
#endregion
#region /* RENDERING */
public override void AsText(CodeWriter writer, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
if (flags.HasFlag(RenderFlags.PrefixSpace))
writer.Write(" ");
AsTextAnnotations(writer, passFlags);
UpdateLineCol(writer, flags);
writer.WriteIdentifier(_name, flags);
}
public static void AsTextTypeParameters(CodeWriter writer, ChildList<TypeParameter> typeParameters, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
// Render the angle brackets as braces if we're inside a documentation comment
writer.Write(flags.HasFlag(RenderFlags.InDocComment) ? ParseTokenAltStart : ParseTokenStart);
writer.WriteList(typeParameters, passFlags, typeParameters.Parent);
writer.Write(flags.HasFlag(RenderFlags.InDocComment) ? ParseTokenAltEnd : ParseTokenEnd);
}
#endregion
}
}