File: TypeNameBuilder.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Legacy\Microsoft.Diagnostics.DataContractReader.Legacy.csproj (Microsoft.Diagnostics.DataContractReader.Legacy)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using Microsoft.Diagnostics.DataContractReader;
using Microsoft.Diagnostics.DataContractReader.Contracts;

namespace Microsoft.Diagnostics.DataContractReader.Legacy;

[Flags]
public enum TypeNameFormat
{
    FormatNamespace = 1,
    FormatFullInst = 2,
    FormatAngleBrackets = 4,
    FormatAssembly = 8,
    FormatGenericParam = 16,
    FormatSignature = 32,
}

public struct TypeNameBuilder
{
    [Flags]
    private enum ParseState
    {
        Start = 1,
        Name = 2,
        GenArgs = 4,
        PtrArr = 8,
        ByRef = 16,
        AssemSpec = 32,
        Error
    }
    private StringBuilder TypeString;
    private Target Target;

    private ParseState State;
    private bool UseAngleBracketsForGenerics { get; init; }
    private bool NestedName;
    private bool HasAssemblySpec;
    private bool FirstInstArg;
    private int InstNesting;
    private Stack<int>? GenericStartsStack;

    private TypeNameBuilder(StringBuilder typeString, Target target, TypeNameFormat format, bool initialStateIsName = false)
    {
        TypeString = typeString;
        Target = target;
        UseAngleBracketsForGenerics = format.HasFlag(TypeNameFormat.FormatAngleBrackets);
        if (initialStateIsName)
            State = ParseState.Name;
        else
            State = ParseState.Start;
    }

    public static void AppendMethodInternal(Target target, StringBuilder stringBuilder, Contracts.MethodDescHandle method, TypeNameFormat format)
    {
        AppendMethodImpl(target, stringBuilder, method, default, format);
    }

    public static void AppendMethodImpl(Target target, StringBuilder stringBuilder, Contracts.MethodDescHandle method, ReadOnlySpan<TypeHandle> typeInstantiation, TypeNameFormat format)
    {
        IRuntimeTypeSystem runtimeTypeSystem = target.Contracts.RuntimeTypeSystem;
        ILoader loader = target.Contracts.Loader;
        string methodName;
        TypeHandle th = default;
        Contracts.ModuleHandle module = default;

        bool isNoMetadataMethod = runtimeTypeSystem.IsNoMetadataMethod(method, out methodName);
        if (isNoMetadataMethod)
        {
            if (runtimeTypeSystem.IsDynamicMethod(method))
            {
                stringBuilder.Append("DynamicClass");
            }
            else if (runtimeTypeSystem.IsILStub(method))
            {
                stringBuilder.Append("ILStubClass");
            }
        }
        else
        {
            th = runtimeTypeSystem.GetTypeHandle(runtimeTypeSystem.GetMethodTable(method));
            AppendType(target, stringBuilder, th, typeInstantiation, format);
        }

        stringBuilder.Append('.');

        if (isNoMetadataMethod)
        {
            stringBuilder.Append(methodName);
        }
        else if (runtimeTypeSystem.IsArrayMethod(method, out ArrayFunctionType functionType))
        {
            string arrayName;

            switch (functionType)
            {
                case ArrayFunctionType.Set:
                    arrayName = "Set";
                    break;
                case ArrayFunctionType.Get:
                    arrayName = "Get";
                    break;
                case ArrayFunctionType.Address:
                    arrayName = "Address";
                    break;
                case ArrayFunctionType.Constructor:
                    arrayName = ".ctor";
                    break;
                default:
                    throw new ArgumentException($"Unkown ArrayFunctionType: {functionType}.", nameof(method));
            }

            stringBuilder.Append(arrayName);
        }
        else
        {
            module = loader.GetModuleHandleFromModulePtr(runtimeTypeSystem.GetModule(th));
            MetadataReader reader = target.Contracts.EcmaMetadata.GetMetadata(module)!;
            MethodDefinition methodDef = reader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)runtimeTypeSystem.GetMethodToken(method)));
            stringBuilder.Append(reader.GetString(methodDef.Name));
        }

        ReadOnlySpan<TypeHandle> genericMethodInstantiation = runtimeTypeSystem.GetGenericMethodInstantiation(method);
        if (genericMethodInstantiation.Length > 0 && !runtimeTypeSystem.IsGenericMethodDefinition(method))
        {
            AppendInst(target, stringBuilder, genericMethodInstantiation, format);
        }

        if (format.HasFlag(TypeNameFormat.FormatSignature))
        {
            ReadOnlySpan<byte> signature;
            MetadataReader? reader = default;
            if (!runtimeTypeSystem.IsStoredSigMethodDesc(method, out signature))
            {
                reader = target.Contracts.EcmaMetadata.GetMetadata(module);
                if (reader is not null)
                {
                    MethodDefinition methodDef = reader.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle((int)runtimeTypeSystem.GetMethodToken(method)));
                    signature = reader.GetBlobBytes(methodDef.Signature);
                }
            }

            ReadOnlySpan<TypeHandle> typeInstantiationSigFormat = default;
            if (!th.IsNull)
            {
                typeInstantiationSigFormat = runtimeTypeSystem.GetInstantiation(th);
                if (typeInstantiationSigFormat.IsEmpty && runtimeTypeSystem.IsArray(th, out _))
                {
                    // For arrays, fill in the instantiation with the element type handle
                    // See MethodTable::GetArrayInstantiation for coreclr equivalent
                    typeInstantiationSigFormat = new[] { runtimeTypeSystem.GetTypeParam(th) };
                }
            }

            SigFormat.AppendSigFormat(target, stringBuilder, signature, reader, null, null, null, typeInstantiationSigFormat, runtimeTypeSystem.GetGenericMethodInstantiation(method), true);
        }
    }

    public static TypeHandle GetExactOwningType(IRuntimeTypeSystem runtimeTypeSystem, TypeHandle possiblyDerivedType, MethodDescHandle method)
    {
        TypeHandle approxOwner = runtimeTypeSystem.GetTypeHandle(runtimeTypeSystem.GetMethodTable(method));

        uint typeDefTokenOfOwner = runtimeTypeSystem.GetTypeDefToken(approxOwner);
        TargetPointer moduleOfOwner = runtimeTypeSystem.GetModule(approxOwner);

        do
        {
            uint typeDefTokenOfPossiblyDerivedType = runtimeTypeSystem.GetTypeDefToken(possiblyDerivedType);
            TargetPointer moduleOfPossiblyDerivedType = runtimeTypeSystem.GetModule(possiblyDerivedType);

            if ((typeDefTokenOfOwner == typeDefTokenOfPossiblyDerivedType) && (moduleOfOwner == moduleOfPossiblyDerivedType))
            {
                return possiblyDerivedType;
            }

            TargetPointer parentTypePointer = runtimeTypeSystem.GetParentMethodTable(possiblyDerivedType);
            if (parentTypePointer.Value == 0)
                throw new InvalidOperationException("Invalid parent type");

            // TODO(cdac) - Consider adding infinite loop detection here
            possiblyDerivedType = runtimeTypeSystem.GetTypeHandle(parentTypePointer);
        } while (true);
    }

    public static void AppendType(Target target, StringBuilder stringBuilder, Contracts.TypeHandle typeHandle, TypeNameFormat format)
    {
        AppendType(target, stringBuilder, typeHandle, default, format);
    }

    public static void AppendType(Target target, StringBuilder stringBuilder, Contracts.TypeHandle typeHandle, ReadOnlySpan<TypeHandle> typeInstantiation, TypeNameFormat format)
    {
        TypeNameBuilder builder = new(stringBuilder, target, format);
        AppendTypeCore(ref builder, typeHandle, typeInstantiation, format);
    }

    private static void AppendTypeCore(ref TypeNameBuilder tnb, Contracts.TypeHandle typeHandle, ReadOnlySpan<Contracts.TypeHandle> instantiation, TypeNameFormat format)
    {
        bool toString = format.HasFlag(TypeNameFormat.FormatNamespace) && !format.HasFlag(TypeNameFormat.FormatFullInst) && !format.HasFlag(TypeNameFormat.FormatAssembly);

        if (typeHandle.IsNull)
        {
            tnb.AddName("(null)");
        }
        else
        {
            var typeSystemContract = tnb.Target.Contracts.RuntimeTypeSystem;
            if (typeSystemContract.HasTypeParam(typeHandle))
            {
                var elemType = typeSystemContract.GetSignatureCorElementType(typeHandle);
                if (elemType != Contracts.CorElementType.ValueType)
                {
                    typeSystemContract.IsArray(typeHandle, out uint rank);
                    AppendTypeCore(ref tnb, typeSystemContract.GetTypeParam(typeHandle), default(ReadOnlySpan<Contracts.TypeHandle>), (TypeNameFormat)(format & ~TypeNameFormat.FormatAssembly));
                    AppendParamTypeQualifier(ref tnb, elemType, rank);
                }
                else
                {
                    tnb.TypeString.Append("VALUETYPE");
                    AppendTypeCore(ref tnb, typeSystemContract.GetTypeParam(typeHandle), Array.Empty<Contracts.TypeHandle>(), format & ~TypeNameFormat.FormatAssembly);
                }
            }
            else if (typeSystemContract.IsGenericVariable(typeHandle, out TargetPointer modulePointer, out uint genericParamToken))
            {
                Contracts.ModuleHandle module = tnb.Target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePointer);
                MetadataReader reader = tnb.Target.Contracts.EcmaMetadata.GetMetadata(module)!;
                var handle = (GenericParameterHandle)MetadataTokens.Handle((int)genericParamToken);
                GenericParameter genericParam = reader.GetGenericParameter(handle);
                if (format.HasFlag(TypeNameFormat.FormatGenericParam))
                {
                    if (genericParam.Parent.Kind == HandleKind.TypeDefinition)
                    {
                        tnb.TypeString.Append('!');
                    }
                    else
                    {
                        tnb.TypeString.Append("!!");
                    }
                }
                tnb.AddName(reader.GetString(genericParam.Name));
                format &= ~TypeNameFormat.FormatAssembly;
            }
            else if (typeSystemContract.IsFunctionPointer(typeHandle, out ReadOnlySpan<TypeHandle> retAndArgTypes, out byte callConv))
            {
                if (format.HasFlag(TypeNameFormat.FormatNamespace))
                {
                    StringBuilder stringBuilder = new();
                    AppendType(tnb.Target, stringBuilder, retAndArgTypes[0], format);
                    stringBuilder.Append('(');
                    for (int i = 1; i < retAndArgTypes.Length; i++)
                    {
                        if (i != 1)
                        {
                            stringBuilder.Append(", ");
                        }
                        AppendType(tnb.Target, stringBuilder, retAndArgTypes[i], format);
                    }

                    if ((callConv & 0x7) == 0x5) // Is this the VARARG calling convention?
                    {
                        if (retAndArgTypes.Length > 2)
                        {
                            stringBuilder.Append(", ");
                        }
                        stringBuilder.Append("...");
                    }
                    stringBuilder.Append(')');
                    tnb.AddNameNoEscaping(stringBuilder);
                }
                else
                {
                    tnb.AddNameNoEscaping(new StringBuilder());
                }
            }
            else
            {
                // ...otherwise it's just a plain type def or an instantiated type
                uint typeDefToken = typeSystemContract.GetTypeDefToken(typeHandle);
                Contracts.ModuleHandle moduleHandle = tnb.Target.Contracts.Loader.GetModuleHandleFromModulePtr(typeSystemContract.GetModule(typeHandle));
                if (MetadataTokens.EntityHandle((int)typeDefToken).IsNil)
                {
                    if (typeSystemContract.IsContinuation(typeHandle))
                    {
                        AppendContinuationName(ref tnb, typeSystemContract, typeHandle);
                    }
                    else
                    {
                        tnb.AddName("(dynamicClass)");
                    }
                }
                else
                {
                    MetadataReader reader = tnb.Target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
                    AppendNestedTypeDef(ref tnb, reader, (TypeDefinitionHandle)MetadataTokens.EntityHandle((int)typeDefToken), format);
                }

                // Append instantiation

                if (format.HasFlag(TypeNameFormat.FormatNamespace) || format.HasFlag(TypeNameFormat.FormatAssembly))
                {
                    ReadOnlySpan<TypeHandle> instantiationSpan = typeSystemContract.GetInstantiation(typeHandle);

                    if ((instantiationSpan.Length > 0) && (!typeSystemContract.IsGenericTypeDefinition(typeHandle) || toString))
                    {
                        if (instantiation.Length == 0)
                        {
                            instantiation = instantiationSpan;
                        }
                        AppendInst(ref tnb, instantiation, format);
                    }
                }
            }
            if (format.HasFlag(TypeNameFormat.FormatAssembly))
            {
                TargetPointer modulePtr = typeSystemContract.GetModule(typeHandle);

                Contracts.ModuleHandle module = tnb.Target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
                // NOTE: The DAC variant of assembly name generation is different than the runtime version. The DAC variant is simpler, and only uses SimpleName

                MetadataReader mr = tnb.Target.Contracts.EcmaMetadata.GetMetadata(module)!;
                string assemblySimpleName = mr.GetString(mr.GetAssemblyDefinition().Name);

                tnb.AddAssemblySpec(assemblySimpleName);
            }
        }
    }

    // Append a square-bracket-enclosed, comma-separated list of n type parameters in inst to the string s
    // and enclose each parameter in square brackets to disambiguate the commas
    // The following flags in the FormatFlags argument are significant: FormatNamespace FormatFullInst FormatAssembly FormatNoVersion
    private static void AppendInst(Target target, StringBuilder stringBuilder, ReadOnlySpan<TypeHandle> inst, TypeNameFormat format)
    {
        TypeNameBuilder tnb = new(stringBuilder, target, format, initialStateIsName: true);
        AppendInst(ref tnb, inst, format);
    }

    private static void AppendInst(ref TypeNameBuilder tnb, ReadOnlySpan<TypeHandle> inst, TypeNameFormat format)
    {
        tnb.OpenGenericArguments();
        foreach (TypeHandle arg in inst)
        {
            tnb.OpenGenericArgument();
            if (format.HasFlag(TypeNameFormat.FormatFullInst) && !tnb.Target.Contracts.RuntimeTypeSystem.IsGenericVariable(arg, out _, out _))
            {
                AppendTypeCore(ref tnb, arg, default, format | TypeNameFormat.FormatNamespace | TypeNameFormat.FormatAssembly);
            }
            else
            {
                AppendTypeCore(ref tnb, arg, default, format & (TypeNameFormat.FormatNamespace | TypeNameFormat.FormatAngleBrackets));
            }
            tnb.CloseGenericArgument();
        }
        tnb.CloseGenericArguments();
    }

    private void OpenGenericArguments()
    {
        if (!CheckParseState(ParseState.Name))
        {
            Fail();
            return;
        }

        State = ParseState.Start;
        InstNesting++;
        FirstInstArg = true;

        TypeString.Append(UseAngleBracketsForGenerics ? '<' : '[');
    }

    private void OpenGenericArgument()
    {
        if (!CheckParseState(ParseState.Start))
        {
            Fail();
            return;
        }
        if (InstNesting == 0)
        {
            Fail();
            return;
        }

        State = ParseState.Start;
        NestedName = false;
        if (!FirstInstArg)
        {
            TypeString.Append(',');
        }
        else
        {
            FirstInstArg = false;
        }

        TypeString.Append(UseAngleBracketsForGenerics ? '<' : '[');
        PushOpenGenericArgument();
    }

    private void CloseGenericArgument()
    {
        if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr | ParseState.ByRef | ParseState.AssemSpec))
        {
            Fail();
            return;
        }
        if (InstNesting == 0)
        {
            Fail();
            return;
        }

        State = ParseState.Start;

        if (HasAssemblySpec)
        {
            TypeString.Append(UseAngleBracketsForGenerics ? '>' : ']');
        }

        PopOpenGenericArgument();
    }

    private void CloseGenericArguments()
    {
        if (InstNesting == 0)
        {
            Fail();
            return;
        }
        if (!CheckParseState(ParseState.Start))
        {
            Fail();
        }

        State = ParseState.GenArgs;
        InstNesting--;

        if (FirstInstArg)
        {
            if (TypeString.Length > 0)
                TypeString.Remove(TypeString.Length - 1, 1);
        }
        else
        {
            TypeString.Append(UseAngleBracketsForGenerics ? '>' : ']');
        }
    }
    private void PushOpenGenericArgument()
    {
        GenericStartsStack ??= new();
        GenericStartsStack.Push(TypeString.Length);
    }

    private void PopOpenGenericArgument()
    {
        int strIndex = GenericStartsStack!.Pop();

        if (!HasAssemblySpec)
        {
            TypeString.Remove(strIndex - 1, 1);
        }
        HasAssemblySpec = false;
    }

    private void AddAssemblySpec(string? assemblySpec)
    {
        if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr | ParseState.ByRef))
        {
            Fail();
            return;
        }

        State = ParseState.AssemSpec;
        if (!string.IsNullOrEmpty(assemblySpec))
        {
            TypeString.Append(", ");

            if (InstNesting > 0)
                EscapeEmbeddedAssemblyName(assemblySpec);
            else
                EscapeAssemblyName(assemblySpec);

            HasAssemblySpec = true;
        }
    }

    /// <summary>
    /// Builds the synthetic name for a dynamically-created continuation method table, mirroring the
    /// native <c>AsyncContinuationsManager::PrintContinuationName</c> in <c>asynccontinuations.h</c>.
    /// </summary>
    /// <remarks>
    /// The name has the form:
    /// <c>Continuation_&lt;dataSize&gt;[_&lt;gcOffset&gt;_&lt;gcCount&gt;]*</c>
    /// where:
    /// <list type="bullet">
    ///   <item><description>
    ///     <c>dataSize</c> is the number of bytes of data payload (base size minus the fixed
    ///     object-header and continuation-header overhead).
    ///   </description></item>
    ///   <item><description>
    ///     Each <c>_gcOffset_gcCount</c> pair describes one GC-pointer run: <c>gcOffset</c> is the
    ///     offset in bytes from the start of the data payload to the run, and <c>gcCount</c> is the
    ///     number of pointer-sized GC references in that run.
    ///   </description></item>
    /// </list>
    /// Only GC descriptor series whose <c>startoffset</c> is at or above the continuation data
    /// payload (i.e., after the fixed <c>CORINFO_Continuation</c> header fields) are included.
    /// </remarks>
    private static void AppendContinuationName(ref TypeNameBuilder tnb, IRuntimeTypeSystem typeSystemContract, TypeHandle typeHandle)
    {
        uint baseSize = typeSystemContract.GetBaseSize(typeHandle);
        uint continuationDataOffset = tnb.Target.GetTypeInfo(DataType.ContinuationObject).Size!.Value;
        uint objHeaderSize = tnb.Target.GetTypeInfo(DataType.ObjectHeader).Size!.Value;
        uint dataSize = baseSize - (objHeaderSize + continuationDataOffset);

        var name = new StringBuilder("Continuation_");
        name.Append(dataSize);

        foreach ((uint seriesOffset, uint seriesSize) in typeSystemContract.GetGCDescSeries(typeHandle))
        {
            if (seriesOffset < continuationDataOffset)
                continue;

            name.Append('_');
            name.Append(seriesOffset - continuationDataOffset);
            name.Append('_');
            name.Append(seriesSize / (uint)tnb.Target.PointerSize);
        }

        tnb.AddNameNoEscaping(name);
    }

    private static void AppendNestedTypeDef(ref TypeNameBuilder tnb, MetadataReader reader, TypeDefinitionHandle typeDefToken, TypeNameFormat format)
    {
        TypeDefinition typeDef = reader.GetTypeDefinition(typeDefToken);
        System.Reflection.TypeAttributes typeDefAttributes = typeDef.Attributes;
        if ((int)(typeDefAttributes & System.Reflection.TypeAttributes.VisibilityMask) >= (int)System.Reflection.TypeAttributes.NestedPublic)
        {
            List<TypeDefinitionHandle> nestedTokens = [];
            for (TypeDefinitionHandle enclosingType = typeDef.GetDeclaringType(); !enclosingType.IsNil; enclosingType = reader.GetTypeDefinition(enclosingType).GetDeclaringType())
            {
                nestedTokens.Add(enclosingType);
            }

            for (int i = nestedTokens.Count - 1; i >= 0; i--)
            {
                AppendTypeDef(ref tnb, reader, reader.GetTypeDefinition(nestedTokens[i]), format);
            }
        }
        AppendTypeDef(ref tnb, reader, typeDef, format);
    }

    private static void AppendTypeDef(ref TypeNameBuilder tnb, MetadataReader reader, TypeDefinition typeDef, TypeNameFormat format)
    {
        string? typeNamespace = null;
        if (format.HasFlag(TypeNameFormat.FormatNamespace))
        {
            typeNamespace = reader.GetString(typeDef.Namespace);
        }
        tnb.AddName(reader.GetString(typeDef.Name), typeNamespace);
    }

    private static void AppendParamTypeQualifier(ref TypeNameBuilder tnb, CorElementType kind, uint rank)
    {
        switch (kind)
        {
            case CorElementType.Byref:
                tnb.AddByRef();
                break;
            case CorElementType.Ptr:
                tnb.AddPointer();
                break;
            case CorElementType.SzArray:
                tnb.AddSzArray();
                break;
            case CorElementType.Array:
                tnb.AddArray(rank);
                break;
        }
    }

    private void AddByRef()
    {
        if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr))
        {
            Fail();
            return;
        }

        State = ParseState.ByRef;
        TypeString.Append('&');
    }

    private void AddPointer()
    {
        if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr))
        {
            Fail();
            return;
        }

        State = ParseState.PtrArr;
        TypeString.Append('*');
    }

    private void AddSzArray()
    {
        if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr))
        {
            Fail();
            return;
        }

        State = ParseState.PtrArr;
        TypeString.Append("[]");
    }

    private void AddArray(uint rank)
    {
        if (!CheckParseState(ParseState.Name | ParseState.GenArgs | ParseState.PtrArr))
        {
            Fail();
            return;
        }

        State = ParseState.PtrArr;
        if (rank == 0)
            return;

        if (rank == 1)
        {
            TypeString.Append("[*]");
        }
        else if (rank > 64)
        {
            TypeString.Append($"[{rank}]");
        }
        else
        {
            TypeString.Append('[');
            for (uint i = 1; i < rank; i++)
            {
                TypeString.Append(',');
            }
            TypeString.Append(']');
        }
    }

    private static ReadOnlySpan<char> TypeNameReservedChars()
    {
        return ",[]&*+\\";
    }

    private static bool IsTypeNameReservedChar(char c)
    {
        return TypeNameReservedChars().Contains(c);
    }

    private void EscapeName(string name)
    {
        foreach (char c in name)
        {
            if (IsTypeNameReservedChar(c))
            {
                TypeString.Append('\\');
            }
            TypeString.Append(c);
        }
    }

    private void AddName(string name, string? _namespace = null)
    {
        if (name == null)
        {
            Fail();
            return;
        }

        if (!CheckParseState(ParseState.Start | ParseState.Name))
        {
            Fail();
            return;
        }

        State = ParseState.Name;
        if (NestedName)
            TypeString.Append('+');

        NestedName = true;
        if (!string.IsNullOrEmpty(_namespace))
        {
            EscapeName(_namespace);
            TypeString.Append('.');
        }

        EscapeName(name);
    }

    private void EscapeAssemblyName(string assemblyName)
    {
        TypeString.Append(assemblyName);
    }

    private void EscapeEmbeddedAssemblyName(string assemblyName)
    {
        foreach (char c in assemblyName)
        {
            if (c == ']')
                TypeString.Append('\\');
            TypeString.Append(c);
        }
    }

    private void AddNameNoEscaping(StringBuilder? name)
    {
        if (name == null)
        {
            Fail();
            return;
        }

        if (!CheckParseState(ParseState.Start | ParseState.Name))
        {
            Fail();
            return;
        }

        State = ParseState.Name;

        if (NestedName)
            TypeString.Append('+');

        NestedName = true;
        TypeString.Append(name);
    }

    private void Fail()
    {
        State = ParseState.Error;
    }

    private bool CheckParseState(ParseState validStates)
    {
        // Error is always invalid
        if (State == ParseState.Error)
            return false;

        return (State & validStates) != 0;
    }
}