File: SymbolDisplay\SymbolDisplayVisitor.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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.SymbolDisplay;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class SymbolDisplayVisitor : AbstractSymbolDisplayVisitor
    {
        private static readonly ObjectPool<SymbolDisplayVisitor> s_visitorPool = new ObjectPool<SymbolDisplayVisitor>(pool => new SymbolDisplayVisitor(pool), 128);
 
        private readonly ObjectPool<SymbolDisplayVisitor> _pool;
 
        private bool _escapeKeywordIdentifiers;
        private IDictionary<INamespaceOrTypeSymbol, IAliasSymbol>? _lazyAliasMap;
 
        private SymbolDisplayVisitor(ObjectPool<SymbolDisplayVisitor> pool)
        {
            _pool = pool;
        }
 
        public static SymbolDisplayVisitor GetInstance(
            ArrayBuilder<SymbolDisplayPart> builder,
            SymbolDisplayFormat format,
            SemanticModel? semanticModelOpt,
            int positionOpt)
        {
            var instance = s_visitorPool.Allocate();
            instance.Initialize(builder, format, isFirstSymbolVisited: true, semanticModelOpt, positionOpt, inNamespaceOrType: false);
            return instance;
        }
 
        private static SymbolDisplayVisitor GetInstance(
            ArrayBuilder<SymbolDisplayPart> builder,
            SymbolDisplayFormat format,
            SemanticModel? semanticModelOpt,
            int positionOpt,
            bool escapeKeywordIdentifiers,
            IDictionary<INamespaceOrTypeSymbol, IAliasSymbol>? aliasMap,
            bool isFirstSymbolVisited,
            bool inNamespaceOrType = false)
        {
            var instance = s_visitorPool.Allocate();
            instance.Initialize(builder, format, isFirstSymbolVisited, semanticModelOpt, positionOpt, inNamespaceOrType);
            instance._escapeKeywordIdentifiers = escapeKeywordIdentifiers;
            instance._lazyAliasMap = aliasMap;
            return instance;
        }
 
        protected new void Initialize(ArrayBuilder<SymbolDisplayPart> builder, SymbolDisplayFormat format, bool isFirstSymbolVisited, SemanticModel? semanticModelOpt, int positionOpt, bool inNamespaceOrType)
        {
            base.Initialize(builder, format, isFirstSymbolVisited, semanticModelOpt, positionOpt, inNamespaceOrType);
 
            _escapeKeywordIdentifiers = format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers);
        }
 
        public override void Free()
        {
            base.Free();
 
            _escapeKeywordIdentifiers = false;
            _lazyAliasMap = null;
 
            _pool.Free(this);
        }
 
        protected override AbstractSymbolDisplayVisitor MakeNotFirstVisitor(bool inNamespaceOrType = false)
        {
            return GetInstance(
                this.Builder,
                this.Format,
                this.SemanticModelOpt,
                this.PositionOpt,
                _escapeKeywordIdentifiers,
                _lazyAliasMap,
                isFirstSymbolVisited: false,
                inNamespaceOrType: inNamespaceOrType);
        }
 
        protected override void FreeNotFirstVisitor(AbstractSymbolDisplayVisitor visitor)
        {
            Debug.Assert(visitor != this);
            visitor.Free();
        }
 
        internal SymbolDisplayPart CreatePart(SymbolDisplayPartKind kind, ISymbol? symbol, string text)
        {
            if (text == null)
            {
                return new SymbolDisplayPart(kind, symbol, "?");
            }
 
            if (!_escapeKeywordIdentifiers)
            {
                return new SymbolDisplayPart(kind, symbol, text);
            }
 
            if (IsEscapable(kind))
            {
                text = EscapeIdentifier(text, symbol?.Kind is SymbolKind.NamedType or SymbolKind.Alias);
            }
 
            return new SymbolDisplayPart(kind, symbol, text);
        }
 
        private static bool IsEscapable(SymbolDisplayPartKind kind)
        {
            switch (kind)
            {
                case SymbolDisplayPartKind.AliasName:
                case SymbolDisplayPartKind.ClassName:
                case SymbolDisplayPartKind.RecordClassName:
                case SymbolDisplayPartKind.StructName:
                case SymbolDisplayPartKind.RecordStructName:
                case SymbolDisplayPartKind.InterfaceName:
                case SymbolDisplayPartKind.EnumName:
                case SymbolDisplayPartKind.DelegateName:
                case SymbolDisplayPartKind.TypeParameterName:
                case SymbolDisplayPartKind.MethodName:
                case SymbolDisplayPartKind.PropertyName:
                case SymbolDisplayPartKind.FieldName:
                case SymbolDisplayPartKind.LocalName:
                case SymbolDisplayPartKind.NamespaceName:
                case SymbolDisplayPartKind.ParameterName:
                    return true;
                default:
                    return false;
            }
        }
 
        private static string EscapeIdentifier(string identifier, bool isNamedTypeOrAliasName)
        {
            SyntaxKind kind = SyntaxFacts.GetKeywordKind(identifier);
 
            if (kind is SyntaxKind.None && isNamedTypeOrAliasName && StringComparer.Ordinal.Equals(identifier, "record"))
            {
                kind = SyntaxKind.RecordKeyword;
            }
 
            return kind == SyntaxKind.None ? identifier : $"@{identifier}";
        }
 
        public override void VisitAssembly(IAssemblySymbol symbol)
        {
            var text = Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameOnly
                ? symbol.Identity.Name
                : symbol.Identity.GetDisplayName();
 
            Builder.Add(CreatePart(SymbolDisplayPartKind.AssemblyName, symbol, text));
        }
 
        public override void VisitModule(IModuleSymbol symbol)
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, symbol.Name));
        }
 
        public override void VisitNamespace(INamespaceSymbol symbol)
        {
            if (this.IsMinimizing)
            {
                if (TryAddAlias(symbol, Builder))
                {
                    return;
                }
 
                MinimallyQualify(symbol);
                return;
            }
 
            if (IsFirstSymbolVisited && Format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeNamespaceKeyword))
            {
                AddKeyword(SyntaxKind.NamespaceKeyword);
                AddSpace();
            }
 
            if (Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)
            {
                var containingNamespace = symbol.ContainingNamespace;
                if (ShouldVisitNamespace(containingNamespace))
                {
                    containingNamespace.Accept(this.NotFirstVisitor);
                    AddPunctuation(containingNamespace.IsGlobalNamespace ? SyntaxKind.ColonColonToken : SyntaxKind.DotToken);
                }
            }
 
            if (symbol.IsGlobalNamespace)
            {
                AddGlobalNamespace(symbol);
            }
            else
            {
                Builder.Add(CreatePart(SymbolDisplayPartKind.NamespaceName, symbol, symbol.Name));
            }
        }
 
        private void AddGlobalNamespace(INamespaceSymbol globalNamespace)
        {
            // Formerly, localized via MessageID.IDS_GlobalNamespace.
            const string standaloneGlobalNamespaceString = "<global namespace>";
 
            switch (Format.GlobalNamespaceStyle)
            {
                case SymbolDisplayGlobalNamespaceStyle.Omitted:
                    break;
                case SymbolDisplayGlobalNamespaceStyle.Included:
                    if (this.IsFirstSymbolVisited)
                    {
                        Builder.Add(CreatePart(
                            SymbolDisplayPartKind.Text,
                            globalNamespace,
                            standaloneGlobalNamespaceString));
                    }
                    else
                    {
                        Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, globalNamespace,
                            SyntaxFacts.GetText(SyntaxKind.GlobalKeyword)));
                    }
                    break;
                case SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining:
                    Debug.Assert(this.IsFirstSymbolVisited, "Don't call with IsFirstSymbolVisited = false if OmittedAsContaining");
                    Builder.Add(CreatePart(
                        SymbolDisplayPartKind.Text,
                        globalNamespace,
                        standaloneGlobalNamespaceString));
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(Format.GlobalNamespaceStyle);
            }
        }
 
        public override void VisitLocal(ILocalSymbol symbol)
        {
            if (Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeModifiers))
            {
                if (symbol.IsRef)
                {
                    if (symbol.ScopedKind == ScopedKind.ScopedRef)
                    {
                        AddKeyword(SyntaxKind.ScopedKeyword);
                        AddSpace();
                    }
 
                    AddKeyword(SyntaxKind.RefKeyword);
                    AddSpace();
 
                    if (symbol.RefKind == RefKind.RefReadOnly)
                    {
                        AddKeyword(SyntaxKind.ReadOnlyKeyword);
                        AddSpace();
                    }
                }
                else if (symbol.ScopedKind == ScopedKind.ScopedValue)
                {
                    AddKeyword(SyntaxKind.ScopedKeyword);
                    AddSpace();
                }
            }
 
            if (Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                symbol.Type.Accept(this.NotFirstVisitor);
                AddSpace();
            }
 
            if (symbol.IsConst)
            {
                Builder.Add(CreatePart(SymbolDisplayPartKind.ConstantName, symbol, symbol.Name));
            }
            else
            {
                Builder.Add(CreatePart(SymbolDisplayPartKind.LocalName, symbol, symbol.Name));
            }
 
            if (Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeConstantValue) &&
                symbol.IsConst &&
                symbol.HasConstantValue &&
                CanAddConstant(symbol.Type, symbol.ConstantValue))
            {
                AddSpace();
                AddPunctuation(SyntaxKind.EqualsToken);
                AddSpace();
 
                AddConstantValue(symbol.Type, symbol.ConstantValue);
            }
        }
 
        public override void VisitDiscard(IDiscardSymbol symbol)
        {
            if (Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                symbol.Type.Accept(this.NotFirstVisitor);
                AddSpace();
            }
 
            Builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "_"));
        }
 
        public override void VisitRangeVariable(IRangeVariableSymbol symbol)
        {
            if (Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                ITypeSymbol? type = GetRangeVariableType(symbol);
 
                if (type != null && type.TypeKind != TypeKind.Error)
                {
                    type.Accept(this);
                }
                else
                {
                    Builder.Add(CreatePart(SymbolDisplayPartKind.ErrorTypeName, type, "?"));
                }
 
                AddSpace();
            }
 
            Builder.Add(CreatePart(SymbolDisplayPartKind.RangeVariableName, symbol, symbol.Name));
        }
 
        public override void VisitLabel(ILabelSymbol symbol)
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.LabelName, symbol, symbol.Name));
        }
 
        public override void VisitAlias(IAliasSymbol symbol)
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.AliasName, symbol, symbol.Name));
 
            if (Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                // ???
                AddPunctuation(SyntaxKind.EqualsToken);
                symbol.Target.Accept(this);
            }
        }
 
        internal override void VisitPreprocessing(IPreprocessingSymbol symbol)
        {
            // Currently using 'Text' part kind as there is no kind specific to preprocessing symbols.
            var part = new SymbolDisplayPart(SymbolDisplayPartKind.Text, symbol, symbol.Name);
            Builder.Add(part);
        }
 
        protected override void AddSpace()
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.Space, null, " "));
        }
 
        private void AddPunctuation(SyntaxKind punctuationKind)
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, null, SyntaxFacts.GetText(punctuationKind)));
        }
 
        private void AddKeyword(SyntaxKind keywordKind)
        {
            Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, null, SyntaxFacts.GetText(keywordKind)));
        }
 
        private void AddAccessibilityIfNeeded(ISymbol symbol)
        {
            INamedTypeSymbol containingType = symbol.ContainingType;
 
            // this method is only called for members and they should have a containingType or a containing symbol should be a TypeSymbol.
            Debug.Assert((object)containingType != null || (symbol.ContainingSymbol is ITypeSymbol));
 
            if (Format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeAccessibility) &&
                (containingType == null ||
                 (containingType.TypeKind != TypeKind.Interface && !IsEnumMember(symbol) & !IsLocalFunction(symbol))))
            {
                AddAccessibility(symbol);
            }
        }
 
        private static bool IsLocalFunction(ISymbol symbol)
        {
            if (symbol.Kind != SymbolKind.Method)
            {
                return false;
            }
 
            return ((IMethodSymbol)symbol).MethodKind == MethodKind.LocalFunction;
        }
 
        private void AddAccessibility(ISymbol symbol)
        {
            switch (symbol.DeclaredAccessibility)
            {
                case Accessibility.Private:
                    AddKeyword(SyntaxKind.PrivateKeyword);
                    break;
                case Accessibility.Internal:
                    AddKeyword(SyntaxKind.InternalKeyword);
                    break;
                case Accessibility.ProtectedAndInternal:
                    AddKeyword(SyntaxKind.PrivateKeyword);
                    AddSpace();
                    AddKeyword(SyntaxKind.ProtectedKeyword);
                    break;
                case Accessibility.Protected:
                    AddKeyword(SyntaxKind.ProtectedKeyword);
                    break;
                case Accessibility.ProtectedOrInternal:
                    AddKeyword(SyntaxKind.ProtectedKeyword);
                    AddSpace();
                    AddKeyword(SyntaxKind.InternalKeyword);
                    break;
                case Accessibility.Public:
                    AddKeyword(SyntaxKind.PublicKeyword);
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.DeclaredAccessibility);
            }
 
            AddSpace();
        }
 
        private bool ShouldVisitNamespace(ISymbol containingSymbol)
        {
            var namespaceSymbol = containingSymbol as INamespaceSymbol;
            if (namespaceSymbol == null)
            {
                return false;
            }
 
            if (Format.TypeQualificationStyle != SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)
            {
                return false;
            }
 
            return
                !namespaceSymbol.IsGlobalNamespace ||
                Format.GlobalNamespaceStyle == SymbolDisplayGlobalNamespaceStyle.Included;
        }
 
        private bool IncludeNamedType([NotNullWhen(true)] INamedTypeSymbol? namedType)
        {
            if (namedType is null)
            {
                return false;
            }
 
            if (namedType.IsScriptClass && !Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeScriptType))
            {
                return false;
            }
 
            if (namedType == SemanticModelOpt?.Compilation.ScriptGlobalsType)
            {
                return false;
            }
 
            return true;
        }
 
        private static bool IsEnumMember(ISymbol symbol)
        {
            return symbol != null
                && symbol.Kind == SymbolKind.Field
                && symbol.ContainingType != null
                && symbol.ContainingType.TypeKind == TypeKind.Enum
                && symbol.Name != WellKnownMemberNames.EnumBackingFieldName;
        }
    }
}