File: SymbolDisplay\SymbolDisplayVisitor.Types.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.SymbolDisplay;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class SymbolDisplayVisitor
    {
        public override void VisitArrayType(IArrayTypeSymbol symbol)
        {
            VisitArrayTypeWithoutNullability(symbol);
            AddNullableAnnotations(symbol);
        }
 
        private void VisitArrayTypeWithoutNullability(IArrayTypeSymbol symbol)
        {
            if (TryAddAlias(symbol, Builder))
            {
                return;
            }
 
            //See spec section 12.1 for the order of rank specifiers
            //e.g. int[][,][,,] is stored as
            //     ArrayType
            //         Rank = 1
            //         ElementType = ArrayType
            //             Rank = 2
            //             ElementType = ArrayType
            //                 Rank = 3
            //                 ElementType = int
 
            if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.ReverseArrayRankSpecifiers))
            {
                // Ironically, reverse order is simpler - we just have to recurse on the element type and then add a rank specifier.
                symbol.ElementType.Accept(this);
                AddArrayRank(symbol);
                return;
            }
 
            ITypeSymbol underlyingType = symbol;
            do
            {
                underlyingType = ((IArrayTypeSymbol)underlyingType).ElementType;
            }
            while (underlyingType.Kind == SymbolKind.ArrayType && !ShouldAddNullableAnnotation(underlyingType));
 
            underlyingType.Accept(this.NotFirstVisitor);
 
            var arrayType = symbol;
            while (arrayType != null && arrayType != underlyingType)
            {
                if (!this.IsFirstSymbolVisited)
                {
                    AddCustomModifiersIfNeeded(arrayType.CustomModifiers, leadingSpace: true);
                }
 
                AddArrayRank(arrayType);
                arrayType = arrayType.ElementType as IArrayTypeSymbol;
            }
        }
 
        private void AddNullableAnnotations(ITypeSymbol type)
        {
            if (ShouldAddNullableAnnotation(type))
            {
                AddPunctuation(type.NullableAnnotation == CodeAnalysis.NullableAnnotation.Annotated ? SyntaxKind.QuestionToken : SyntaxKind.ExclamationToken);
            }
        }
 
        private bool ShouldAddNullableAnnotation(ITypeSymbol type)
        {
            switch (type.NullableAnnotation)
            {
                case CodeAnalysis.NullableAnnotation.Annotated:
                    if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) &&
                        !ITypeSymbolHelpers.IsNullableType(type) && !type.IsValueType)
                    {
                        return true;
                    }
                    break;
 
                case CodeAnalysis.NullableAnnotation.NotAnnotated:
                    if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNotNullableReferenceTypeModifier) &&
                        !type.IsValueType &&
                        (type as Symbols.PublicModel.TypeSymbol)?.UnderlyingTypeSymbol.IsTypeParameterDisallowingAnnotationInCSharp8() != true)
                    {
                        return true;
                    }
                    break;
            }
 
            return false;
        }
 
        private void AddArrayRank(IArrayTypeSymbol symbol)
        {
            bool insertStars = Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.UseAsterisksInMultiDimensionalArrays);
 
            AddPunctuation(SyntaxKind.OpenBracketToken);
 
            if (symbol.Rank > 1)
            {
                if (insertStars)
                {
                    AddPunctuation(SyntaxKind.AsteriskToken);
                }
            }
            else
            {
                if (!symbol.IsSZArray)
                {
                    // Always add an asterisk in this case in order to distinguish between SZArray and MDArray.
                    AddPunctuation(SyntaxKind.AsteriskToken);
                }
            }
 
            for (int i = 0; i < symbol.Rank - 1; i++)
            {
                AddPunctuation(SyntaxKind.CommaToken);
 
                if (insertStars)
                {
                    AddPunctuation(SyntaxKind.AsteriskToken);
                }
            }
 
            AddPunctuation(SyntaxKind.CloseBracketToken);
        }
 
        public override void VisitPointerType(IPointerTypeSymbol symbol)
        {
            symbol.PointedAtType.Accept(this.NotFirstVisitor);
 
            AddNullableAnnotations(symbol);
 
            if (!this.IsFirstSymbolVisited)
            {
                AddCustomModifiersIfNeeded(symbol.CustomModifiers, leadingSpace: true);
            }
            AddPunctuation(SyntaxKind.AsteriskToken);
        }
 
        public override void VisitFunctionPointerType(IFunctionPointerTypeSymbol symbol)
        {
            VisitMethod(symbol.Signature);
        }
 
        public override void VisitTypeParameter(ITypeParameterSymbol symbol)
        {
            if (this.IsFirstSymbolVisited)
            {
                AddTypeParameterVarianceIfNeeded(symbol);
            }
 
            //variance and constraints are handled by methods and named types
            Builder.Add(CreatePart(SymbolDisplayPartKind.TypeParameterName, symbol, symbol.Name));
 
            AddNullableAnnotations(symbol);
        }
 
        public override void VisitDynamicType(IDynamicTypeSymbol symbol)
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, symbol.Name));
 
            AddNullableAnnotations(symbol);
        }
 
        public override void VisitNamedType(INamedTypeSymbol symbol)
        {
            if ((Format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeFileLocalTypesPrefix) != 0
                && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { } internalSymbol1 }
                && internalSymbol1.GetFileLocalTypeMetadataNamePrefix() is { } fileLocalNamePrefix)
            {
                Builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, fileLocalNamePrefix));
            }
 
            VisitNamedTypeWithoutNullability(symbol);
            AddNullableAnnotations(symbol);
 
            if ((Format.CompilerInternalOptions & SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes) != 0
                && symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: NamedTypeSymbol { AssociatedFileIdentifier: { } identifier } internalSymbol2 })
            {
                var fileDescription = identifier.DisplayFilePath is { Length: not 0 } path ? path
                    : internalSymbol2.GetFirstLocationOrNone().SourceTree is { } tree ? $"<tree {internalSymbol2.DeclaringCompilation.GetSyntaxTreeOrdinal(tree)}>"
                    : "<unknown>";
 
                Builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "@"));
                Builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, fileDescription));
            }
        }
 
        private void VisitNamedTypeWithoutNullability(INamedTypeSymbol symbol)
        {
            if (this.IsMinimizing && TryAddAlias(symbol, Builder))
            {
                return;
            }
 
            if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.UseSpecialTypes) ||
                (symbol.IsNativeIntegerType && !Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType)))
            {
                if (AddSpecialTypeKeyword(symbol))
                {
                    //if we're using special type keywords and this is a special type, then no other work is required
                    return;
                }
            }
 
            if (!Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.ExpandNullable))
            {
                //if we're expanding nullable, we just visit nullable types normally
                if (ITypeSymbolHelpers.IsNullableType(symbol) && !symbol.IsDefinition)
                {
                    // Can't have a type called "int*?".
                    var typeArg = symbol.TypeArguments[0];
                    if (typeArg.TypeKind != TypeKind.Pointer)
                    {
                        typeArg.Accept(this.NotFirstVisitor);
                        AddCustomModifiersIfNeeded(symbol.GetTypeArgumentCustomModifiers(0), leadingSpace: true, trailingSpace: false);
 
                        AddPunctuation(SyntaxKind.QuestionToken);
 
                        //visiting the underlying type did all of the work for us
                        return;
                    }
                }
            }
 
            if (this.IsMinimizing || (symbol.IsTupleType && !ShouldDisplayAsValueTuple(symbol)))
            {
                MinimallyQualify(symbol);
                return;
            }
 
            AddTypeKind(symbol);
 
            if (CanShowDelegateSignature(symbol))
            {
                Debug.Assert(symbol.DelegateInvokeMethod is not null);
 
                if (Format.DelegateStyle == SymbolDisplayDelegateStyle.NameAndSignature)
                {
                    var invokeMethod = symbol.DelegateInvokeMethod;
                    if (invokeMethod.ReturnsByRef)
                    {
                        AddRefIfNeeded();
                    }
                    else if (invokeMethod.ReturnsByRefReadonly)
                    {
                        AddRefReadonlyIfNeeded();
                    }
 
                    if (invokeMethod.ReturnsVoid)
                    {
                        AddKeyword(SyntaxKind.VoidKeyword);
                    }
                    else
                    {
                        AddReturnType(symbol.DelegateInvokeMethod);
                    }
 
                    AddSpace();
                }
            }
 
            //only visit the namespace if the style requires it and there isn't an enclosing type
            var containingSymbol = symbol.ContainingSymbol;
            if (ShouldVisitNamespace(containingSymbol))
            {
                var namespaceSymbol = (INamespaceSymbol)containingSymbol;
                var shouldSkip = namespaceSymbol.IsGlobalNamespace && symbol.TypeKind == TypeKind.Error;
 
                if (!shouldSkip)
                {
                    namespaceSymbol.Accept(this.NotFirstVisitor);
                    AddPunctuation(namespaceSymbol.IsGlobalNamespace ? SyntaxKind.ColonColonToken : SyntaxKind.DotToken);
                }
            }
 
            //visit the enclosing type if the style requires it
            if (Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypes ||
                Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)
            {
                if (IncludeNamedType(symbol.ContainingType))
                {
                    symbol.ContainingType.Accept(this.NotFirstVisitor);
 
                    if (Format.CompilerInternalOptions.HasFlag(SymbolDisplayCompilerInternalOptions.UsePlusForNestedTypes))
                    {
                        AddPunctuation(SyntaxKind.PlusToken);
                    }
                    else
                    {
                        AddPunctuation(SyntaxKind.DotToken);
                    }
                }
            }
 
            AddNameAndTypeArgumentsOrParameters(symbol);
        }
 
        private bool ShouldDisplayAsValueTuple(INamedTypeSymbol symbol)
        {
            Debug.Assert(symbol.IsTupleType);
 
            if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.ExpandValueTuple))
            {
                return true;
            }
 
            return !CanUseTupleSyntax(symbol);
        }
 
        private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol)
        {
            if (symbol.IsAnonymousType && symbol.TypeKind != TypeKind.Delegate)
            {
                AddAnonymousTypeName(symbol);
                return;
            }
            else if (symbol.IsTupleType && !ShouldDisplayAsValueTuple(symbol))
            {
                AddTupleTypeName(symbol);
                return;
            }
 
            string? symbolName = null;
 
            // It would be nice to handle VB NoPia symbols too, but it's not worth the effort.
 
            NamedTypeSymbol? underlyingTypeSymbol = (symbol as Symbols.PublicModel.NamedTypeSymbol)?.UnderlyingNamedTypeSymbol;
            var illegalGenericInstantiationSymbol = underlyingTypeSymbol as NoPiaIllegalGenericInstantiationSymbol;
 
            if (illegalGenericInstantiationSymbol is not null)
            {
                symbol = illegalGenericInstantiationSymbol.UnderlyingSymbol.GetPublicSymbol();
            }
            else
            {
                var ambiguousCanonicalTypeSymbol = underlyingTypeSymbol as NoPiaAmbiguousCanonicalTypeSymbol;
                if (ambiguousCanonicalTypeSymbol is not null)
                {
                    symbol = ambiguousCanonicalTypeSymbol.FirstCandidate.GetPublicSymbol();
                }
                else
                {
                    var missingCanonicalTypeSymbol = underlyingTypeSymbol as NoPiaMissingCanonicalTypeSymbol;
 
                    if (missingCanonicalTypeSymbol is not null)
                    {
                        symbolName = missingCanonicalTypeSymbol.FullTypeName;
                    }
                }
            }
 
            if (symbolName is null && symbol.IsAnonymousType && symbol.TypeKind == TypeKind.Delegate)
            {
                symbolName = "<anonymous delegate>";
            }
 
            var partKind = GetPartKind(symbol);
 
            symbolName ??= symbol.Name;
 
            if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName) &&
                partKind == SymbolDisplayPartKind.ErrorTypeName &&
                string.IsNullOrEmpty(symbolName))
            {
                Builder.Add(CreatePart(partKind, symbol, "?"));
            }
            else
            {
                symbolName = RemoveAttributeSuffixIfNecessary(symbol, symbolName);
                Builder.Add(CreatePart(partKind, symbol, symbolName));
            }
 
            if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes))
            {
                // Only the compiler can set the internal option and the compiler doesn't use other implementations of INamedTypeSymbol.
                if (underlyingTypeSymbol?.MangleName == true)
                {
                    Debug.Assert(symbol.Arity > 0);
                    Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Arity, null,
                        MetadataHelpers.GetAritySuffix(symbol.Arity)));
                }
            }
            else if (symbol.Arity > 0 && Format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeTypeParameters))
            {
                // It would be nice to handle VB symbols too, but it's not worth the effort.
                if (underlyingTypeSymbol is UnsupportedMetadataTypeSymbol || underlyingTypeSymbol is MissingMetadataTypeSymbol || symbol.IsUnboundGenericType)
                {
                    AddPunctuation(SyntaxKind.LessThanToken);
                    for (int i = 0; i < symbol.Arity - 1; i++)
                    {
                        AddPunctuation(SyntaxKind.CommaToken);
                    }
 
                    AddPunctuation(SyntaxKind.GreaterThanToken);
                }
                else
                {
                    AddTypeArguments(symbol, GetTypeArgumentsModifiers(underlyingTypeSymbol));
                    AddDelegateParameters(symbol);
 
                    // TODO: do we want to skip these if we're being visited as a containing type?
                    AddTypeParameterConstraints(symbol.TypeArguments);
                }
            }
            else
            {
                AddDelegateParameters(symbol);
            }
 
            // Only the compiler can set the internal option and the compiler doesn't use other implementations of INamedTypeSymbol.
            if (underlyingTypeSymbol?.OriginalDefinition is MissingMetadataTypeSymbol &&
                Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes))
            {
                //add it as punctuation - it's just for testing
                AddPunctuation(SyntaxKind.OpenBracketToken);
                Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Other, symbol, "missing"));
                AddPunctuation(SyntaxKind.CloseBracketToken);
            }
        }
 
        private ImmutableArray<ImmutableArray<CustomModifier>> GetTypeArgumentsModifiers(NamedTypeSymbol? underlyingTypeSymbol)
        {
            if (this.Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers))
            {
                if ((object?)underlyingTypeSymbol != null)
                {
                    return underlyingTypeSymbol.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.SelectAsArray(a => a.CustomModifiers);
                }
            }
 
            return default;
        }
 
        private void AddDelegateParameters(INamedTypeSymbol symbol)
        {
            if (CanShowDelegateSignature(symbol))
            {
                Debug.Assert(symbol.DelegateInvokeMethod is not null);
                if (Format.DelegateStyle == SymbolDisplayDelegateStyle.NameAndParameters ||
                    Format.DelegateStyle == SymbolDisplayDelegateStyle.NameAndSignature)
                {
                    var method = symbol.DelegateInvokeMethod;
                    AddPunctuation(SyntaxKind.OpenParenToken);
                    AddParametersIfNeeded(hasThisParameter: false, isVarargs: method.IsVararg, parameters: method.Parameters);
                    AddPunctuation(SyntaxKind.CloseParenToken);
                }
            }
        }
 
        private void AddAnonymousTypeName(INamedTypeSymbol symbol)
        {
            // TODO: revise to generate user-friendly name 
            var members = string.Join(", ", symbol.GetMembers().OfType<IPropertySymbol>().Select(CreateAnonymousTypeMember));
 
            if (members.Length == 0)
            {
                Builder.Add(new SymbolDisplayPart(SymbolDisplayPartKind.ClassName, symbol, "<empty anonymous type>"));
            }
            else
            {
                var name = $"<anonymous type: {members}>";
                Builder.Add(new SymbolDisplayPart(SymbolDisplayPartKind.ClassName, symbol, name));
            }
        }
 
        /// <summary>
        /// Returns true if tuple type syntax can be used to refer to the tuple type without loss of information.
        /// For example, it cannot be used when extension tuple is using non-default friendly names. 
        /// </summary>
        /// <param name="tupleSymbol"></param>
        /// <returns></returns>
        private bool CanUseTupleSyntax(INamedTypeSymbol tupleSymbol)
        {
            if (containsModopt(tupleSymbol))
            {
                return false;
            }
 
            INamedTypeSymbol currentUnderlying = GetTupleUnderlyingTypeOrSelf(tupleSymbol);
            if (currentUnderlying.Arity <= 1)
            {
                return false;
            }
 
            while (currentUnderlying.Arity == NamedTypeSymbol.ValueTupleRestPosition)
            {
                tupleSymbol = (INamedTypeSymbol)currentUnderlying.TypeArguments[NamedTypeSymbol.ValueTupleRestPosition - 1];
                Debug.Assert(tupleSymbol.IsTupleType);
 
                if (tupleSymbol.TypeKind == TypeKind.Error ||
                    HasNonDefaultTupleElements(tupleSymbol) ||
                    containsModopt(tupleSymbol))
                {
                    return false;
                }
 
                currentUnderlying = GetTupleUnderlyingTypeOrSelf(tupleSymbol);
            }
 
            return true;
 
            bool containsModopt(INamedTypeSymbol symbol)
            {
                NamedTypeSymbol? underlyingTypeSymbol = (symbol as Symbols.PublicModel.NamedTypeSymbol)?.UnderlyingNamedTypeSymbol;
                ImmutableArray<ImmutableArray<CustomModifier>> modifiers = GetTypeArgumentsModifiers(underlyingTypeSymbol);
                if (modifiers.IsDefault)
                {
                    return false;
                }
 
                return modifiers.Any(static m => !m.IsEmpty);
            }
        }
 
        private static INamedTypeSymbol GetTupleUnderlyingTypeOrSelf(INamedTypeSymbol type)
        {
            return type.TupleUnderlyingType ?? type;
        }
 
        private static bool HasNonDefaultTupleElements(INamedTypeSymbol tupleSymbol)
        {
            return tupleSymbol.TupleElements.Any(static e => !e.IsDefaultTupleElement());
        }
 
        private void AddTupleTypeName(INamedTypeSymbol symbol)
        {
            Debug.Assert(symbol.IsTupleType);
 
            if (this.Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.CollapseTupleTypes))
            {
                Builder.Add(CreatePart(SymbolDisplayPartKind.StructName, symbol, "<tuple>"));
                return;
            }
 
            ImmutableArray<IFieldSymbol> elements = symbol.TupleElements;
 
            AddPunctuation(SyntaxKind.OpenParenToken);
            for (int i = 0; i < elements.Length; i++)
            {
                var element = elements[i];
 
                if (i != 0)
                {
                    AddPunctuation(SyntaxKind.CommaToken);
                    AddSpace();
                }
 
                VisitFieldType(element);
                if (element.IsExplicitlyNamedTupleElement)
                {
                    AddSpace();
                    Builder.Add(CreatePart(SymbolDisplayPartKind.FieldName, element, element.Name));
                }
            }
 
            AddPunctuation(SyntaxKind.CloseParenToken);
 
            if (symbol.TypeKind == TypeKind.Error &&
                Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes))
            {
                //add it as punctuation - it's just for testing
                AddPunctuation(SyntaxKind.OpenBracketToken);
                Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Other, symbol, "missing"));
                AddPunctuation(SyntaxKind.CloseBracketToken);
            }
        }
 
        private string CreateAnonymousTypeMember(IPropertySymbol property)
        {
            return property.Type.ToDisplayString(Format) + " " + property.Name;
        }
 
        private bool CanShowDelegateSignature(INamedTypeSymbol symbol)
        {
            return
                IsFirstSymbolVisited &&
                symbol.TypeKind == TypeKind.Delegate &&
                Format.DelegateStyle != SymbolDisplayDelegateStyle.NameOnly &&
                symbol.DelegateInvokeMethod != null;
        }
 
        private static SymbolDisplayPartKind GetPartKind(INamedTypeSymbol symbol)
        {
            switch (symbol.TypeKind)
            {
                case TypeKind.Class when symbol.IsRecord:
                    return SymbolDisplayPartKind.RecordClassName;
                case TypeKind.Struct when symbol.IsRecord:
                    return SymbolDisplayPartKind.RecordStructName;
                case TypeKind.Submission:
                case TypeKind.Module:
                case TypeKind.Class:
                    return SymbolDisplayPartKind.ClassName;
                case TypeKind.Delegate:
                    return SymbolDisplayPartKind.DelegateName;
                case TypeKind.Enum:
                    return SymbolDisplayPartKind.EnumName;
                case TypeKind.Error:
                    return SymbolDisplayPartKind.ErrorTypeName;
                case TypeKind.Interface:
                    return SymbolDisplayPartKind.InterfaceName;
                case TypeKind.Struct:
                    return SymbolDisplayPartKind.StructName;
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.TypeKind);
            }
        }
 
        private bool AddSpecialTypeKeyword(INamedTypeSymbol symbol)
        {
            var specialTypeName = GetSpecialTypeName(symbol);
            if (specialTypeName == null)
            {
                return false;
            }
 
            // cheat - skip escapeKeywordIdentifiers. not calling AddKeyword because someone
            // else is working out the text for us
            Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, specialTypeName));
            return true;
        }
 
        private static string? GetSpecialTypeName(INamedTypeSymbol symbol)
        {
            switch (symbol.SpecialType)
            {
                case SpecialType.System_Void:
                    return "void";
                case SpecialType.System_SByte:
                    return "sbyte";
                case SpecialType.System_Int16:
                    return "short";
                case SpecialType.System_Int32:
                    return "int";
                case SpecialType.System_Int64:
                    return "long";
                case SpecialType.System_IntPtr when symbol.IsNativeIntegerType:
                    return "nint";
                case SpecialType.System_UIntPtr when symbol.IsNativeIntegerType:
                    return "nuint";
                case SpecialType.System_Byte:
                    return "byte";
                case SpecialType.System_UInt16:
                    return "ushort";
                case SpecialType.System_UInt32:
                    return "uint";
                case SpecialType.System_UInt64:
                    return "ulong";
                case SpecialType.System_Single:
                    return "float";
                case SpecialType.System_Double:
                    return "double";
                case SpecialType.System_Decimal:
                    return "decimal";
                case SpecialType.System_Char:
                    return "char";
                case SpecialType.System_Boolean:
                    return "bool";
                case SpecialType.System_String:
                    return "string";
                case SpecialType.System_Object:
                    return "object";
                default:
                    return null;
            }
        }
 
        private void AddTypeKind(INamedTypeSymbol symbol)
        {
            if (IsFirstSymbolVisited && Format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeTypeKeyword))
            {
                if (symbol.IsAnonymousType && symbol.TypeKind != TypeKind.Delegate)
                {
                    Builder.Add(new SymbolDisplayPart(SymbolDisplayPartKind.AnonymousTypeIndicator, null, "AnonymousType"));
                    AddSpace();
                }
                else if (symbol.IsTupleType && !ShouldDisplayAsValueTuple(symbol))
                {
                    Builder.Add(new SymbolDisplayPart(SymbolDisplayPartKind.AnonymousTypeIndicator, null, "Tuple"));
                    AddSpace();
                }
                else
                {
                    switch (symbol.TypeKind)
                    {
                        case TypeKind.Class when symbol.IsRecord:
                            AddKeyword(SyntaxKind.RecordKeyword);
                            AddSpace();
                            break;
 
                        case TypeKind.Struct when symbol.IsRecord:
                            // In case ref record structs are allowed in future, call AddKeyword(SyntaxKind.RefKeyword) and remove assertion.
                            Debug.Assert(!symbol.IsRefLikeType);
 
                            if (symbol.IsReadOnly)
                            {
                                AddKeyword(SyntaxKind.ReadOnlyKeyword);
                                AddSpace();
                            }
 
                            AddKeyword(SyntaxKind.RecordKeyword);
                            AddSpace();
                            AddKeyword(SyntaxKind.StructKeyword);
                            AddSpace();
                            break;
 
                        case TypeKind.Module:
                        case TypeKind.Class:
                            AddKeyword(SyntaxKind.ClassKeyword);
                            AddSpace();
                            break;
 
                        case TypeKind.Enum:
                            AddKeyword(SyntaxKind.EnumKeyword);
                            AddSpace();
                            break;
 
                        case TypeKind.Delegate:
                            AddKeyword(SyntaxKind.DelegateKeyword);
                            AddSpace();
                            break;
 
                        case TypeKind.Interface:
                            AddKeyword(SyntaxKind.InterfaceKeyword);
                            AddSpace();
                            break;
 
                        case TypeKind.Struct:
                            if (symbol.IsReadOnly)
                            {
                                AddKeyword(SyntaxKind.ReadOnlyKeyword);
                                AddSpace();
                            }
 
                            if (symbol.IsRefLikeType)
                            {
                                AddKeyword(SyntaxKind.RefKeyword);
                                AddSpace();
                            }
 
                            AddKeyword(SyntaxKind.StructKeyword);
                            AddSpace();
                            break;
                    }
                }
            }
        }
 
        private void AddTypeParameterVarianceIfNeeded(ITypeParameterSymbol symbol)
        {
            if (Format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeVariance))
            {
                switch (symbol.Variance)
                {
                    case VarianceKind.In:
                        AddKeyword(SyntaxKind.InKeyword);
                        AddSpace();
                        break;
                    case VarianceKind.Out:
                        AddKeyword(SyntaxKind.OutKeyword);
                        AddSpace();
                        break;
                }
            }
        }
 
        //returns true if there are constraints
        private void AddTypeArguments(ISymbol owner, ImmutableArray<ImmutableArray<CustomModifier>> modifiers)
        {
            ImmutableArray<ITypeSymbol> typeArguments;
 
            if (owner.Kind == SymbolKind.Method)
            {
                typeArguments = ((IMethodSymbol)owner).TypeArguments;
            }
            else
            {
                typeArguments = ((INamedTypeSymbol)owner).TypeArguments;
            }
 
            if (typeArguments.Length > 0 && Format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeTypeParameters))
            {
                AddPunctuation(SyntaxKind.LessThanToken);
 
                var first = true;
                for (int i = 0; i < typeArguments.Length; i++)
                {
                    var typeArg = typeArguments[i];
 
                    if (!first)
                    {
                        AddPunctuation(SyntaxKind.CommaToken);
                        AddSpace();
                    }
                    first = false;
 
                    AbstractSymbolDisplayVisitor visitor;
 
                    if (typeArg.Kind == SymbolKind.TypeParameter)
                    {
                        var typeParam = (ITypeParameterSymbol)typeArg;
 
                        AddTypeParameterVarianceIfNeeded(typeParam);
 
                        visitor = this.NotFirstVisitor;
                    }
                    else
                    {
                        visitor = this.NotFirstVisitorNamespaceOrType;
                    }
 
                    typeArg.Accept(visitor);
 
                    if (!modifiers.IsDefault)
                    {
                        AddCustomModifiersIfNeeded(modifiers[i], leadingSpace: true, trailingSpace: false);
                    }
                }
 
                AddPunctuation(SyntaxKind.GreaterThanToken);
            }
        }
 
        private static bool TypeParameterHasConstraints(ITypeParameterSymbol typeParam)
        {
            return !typeParam.ConstraintTypes.IsEmpty || typeParam.HasConstructorConstraint ||
                typeParam.HasReferenceTypeConstraint || typeParam.HasValueTypeConstraint ||
                typeParam.HasNotNullConstraint || typeParam.AllowsRefLikeType;
        }
 
        private void AddTypeParameterConstraints(ImmutableArray<ITypeSymbol> typeArguments)
        {
            if (this.IsFirstSymbolVisited && Format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeTypeConstraints))
            {
                foreach (var typeArg in typeArguments)
                {
                    if (typeArg.Kind == SymbolKind.TypeParameter)
                    {
                        var typeParam = (ITypeParameterSymbol)typeArg;
 
                        if (TypeParameterHasConstraints(typeParam))
                        {
                            AddSpace();
                            AddKeyword(SyntaxKind.WhereKeyword);
                            AddSpace();
 
                            typeParam.Accept(this.NotFirstVisitor);
 
                            AddSpace();
                            AddPunctuation(SyntaxKind.ColonToken);
                            AddSpace();
 
                            bool needComma = false;
 
                            //class/struct constraint must be first
                            if (typeParam.HasReferenceTypeConstraint)
                            {
                                AddKeyword(SyntaxKind.ClassKeyword);
 
                                switch (typeParam.ReferenceTypeConstraintNullableAnnotation)
                                {
                                    case CodeAnalysis.NullableAnnotation.Annotated:
                                        if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier))
                                        {
                                            AddPunctuation(SyntaxKind.QuestionToken);
                                        }
                                        break;
 
                                    case CodeAnalysis.NullableAnnotation.NotAnnotated:
                                        if (Format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.IncludeNotNullableReferenceTypeModifier))
                                        {
                                            AddPunctuation(SyntaxKind.ExclamationToken);
                                        }
                                        break;
                                }
 
                                needComma = true;
                            }
                            else if (typeParam.HasUnmanagedTypeConstraint)
                            {
                                Builder.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, null, "unmanaged"));
                                needComma = true;
                            }
                            else if (typeParam.HasValueTypeConstraint)
                            {
                                AddKeyword(SyntaxKind.StructKeyword);
                                needComma = true;
                            }
                            else if (typeParam.HasNotNullConstraint)
                            {
                                Builder.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Keyword, null, "notnull"));
                                needComma = true;
                            }
 
                            for (int i = 0; i < typeParam.ConstraintTypes.Length; i++)
                            {
                                ITypeSymbol baseType = typeParam.ConstraintTypes[i];
                                if (needComma)
                                {
                                    AddPunctuation(SyntaxKind.CommaToken);
                                    AddSpace();
                                }
 
                                baseType.Accept(this.NotFirstVisitor);
                                needComma = true;
                            }
 
                            //ctor constraint must be last before 'allows ref struct'
                            if (typeParam.HasConstructorConstraint)
                            {
                                if (needComma)
                                {
                                    AddPunctuation(SyntaxKind.CommaToken);
                                    AddSpace();
                                }
 
                                AddKeyword(SyntaxKind.NewKeyword);
                                AddPunctuation(SyntaxKind.OpenParenToken);
                                AddPunctuation(SyntaxKind.CloseParenToken);
                                needComma = true;
                            }
 
                            if (typeParam.AllowsRefLikeType)
                            {
                                if (needComma)
                                {
                                    AddPunctuation(SyntaxKind.CommaToken);
                                    AddSpace();
                                }
 
                                AddKeyword(SyntaxKind.AllowsKeyword);
                                AddSpace();
                                AddKeyword(SyntaxKind.RefKeyword);
                                AddSpace();
                                AddKeyword(SyntaxKind.StructKeyword);
                            }
                        }
                    }
                }
            }
        }
    }
}