File: SymbolDisplay\AbstractSymbolDisplayVisitor.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.SymbolDisplay
{
    internal abstract partial class AbstractSymbolDisplayVisitor : SymbolVisitor
    {
        // These values will be set to non-null by 'Allocate' when an instance is obtained from the pool
        private ArrayBuilder<SymbolDisplayPart> _builder = null!;
        private SymbolDisplayFormat _format = null!;
        private bool _isFirstSymbolVisited;
        private bool _inNamespaceOrType;
 
        private SemanticModel? _semanticModelOpt;
        private int _positionOpt;
 
        private AbstractSymbolDisplayVisitor? _lazyNotFirstVisitor;
        private AbstractSymbolDisplayVisitor? _lazyNotFirstVisitorNamespaceOrType;
 
        protected AbstractSymbolDisplayVisitor()
        {
        }
 
        protected ArrayBuilder<SymbolDisplayPart> Builder => _builder;
 
        protected SymbolDisplayFormat Format => _format;
 
        protected bool IsFirstSymbolVisited => _isFirstSymbolVisited;
 
        protected bool InNamespaceOrType => _inNamespaceOrType;
 
        protected SemanticModel? SemanticModelOpt => _semanticModelOpt;
 
        protected int PositionOpt => _positionOpt;
 
        protected AbstractSymbolDisplayVisitor NotFirstVisitor
        {
            get
            {
                if (_lazyNotFirstVisitor == null)
                {
                    _lazyNotFirstVisitor = MakeNotFirstVisitor();
                }
 
                return _lazyNotFirstVisitor;
            }
        }
 
        protected AbstractSymbolDisplayVisitor NotFirstVisitorNamespaceOrType
        {
            get
            {
                if (_lazyNotFirstVisitorNamespaceOrType == null)
                {
                    _lazyNotFirstVisitorNamespaceOrType = MakeNotFirstVisitor(inNamespaceOrType: true);
                }
 
                return _lazyNotFirstVisitorNamespaceOrType;
            }
        }
 
        protected void Initialize(
            ArrayBuilder<SymbolDisplayPart> builder,
            SymbolDisplayFormat format,
            bool isFirstSymbolVisited,
            SemanticModel? semanticModelOpt,
            int positionOpt,
            bool inNamespaceOrType)
        {
            Debug.Assert(format != null);
 
            if (_format is not null)
            {
                // Should not be re-initializing a visitor which is already in use.
                throw new InvalidOperationException();
            }
 
            _builder = builder;
            _format = format;
            _isFirstSymbolVisited = isFirstSymbolVisited;
 
            _semanticModelOpt = semanticModelOpt;
            _positionOpt = positionOpt;
            _inNamespaceOrType = inNamespaceOrType;
 
            // If we're not the first symbol visitor, then we will just recurse into ourselves.
            if (!isFirstSymbolVisited)
            {
                _lazyNotFirstVisitor = this;
            }
        }
 
        public virtual void Free()
        {
            if (_lazyNotFirstVisitor != this && _lazyNotFirstVisitor is not null)
                FreeNotFirstVisitor(_lazyNotFirstVisitor);
 
            if (_lazyNotFirstVisitorNamespaceOrType != this && _lazyNotFirstVisitorNamespaceOrType is not null)
                FreeNotFirstVisitor(_lazyNotFirstVisitorNamespaceOrType);
 
            _builder = null!;
            _isFirstSymbolVisited = false;
            _inNamespaceOrType = false;
 
            _semanticModelOpt = null;
            _positionOpt = 0;
            _lazyNotFirstVisitor = null;
            _lazyNotFirstVisitorNamespaceOrType = null;
 
            _format = null!;
        }
 
        protected abstract AbstractSymbolDisplayVisitor MakeNotFirstVisitor(bool inNamespaceOrType = false);
 
        protected abstract void FreeNotFirstVisitor(AbstractSymbolDisplayVisitor visitor);
 
        protected abstract void AddLiteralValue(SpecialType type, object value);
        protected abstract void AddExplicitlyCastedLiteralValue(INamedTypeSymbol namedType, SpecialType type, object value);
        protected abstract void AddSpace();
        protected abstract void AddBitwiseOr();
 
        /// <summary>
        /// Append a default argument (i.e. the default argument of an optional parameter).
        /// Assumed to be non-null.
        /// </summary>
        protected void AddNonNullConstantValue(ITypeSymbol type, object constantValue, bool preferNumericValueOrExpandedFlagsForEnum = false)
        {
            Debug.Assert(constantValue != null);
 
            if (ITypeSymbolHelpers.IsNullableType(type))
            {
                type = ITypeSymbolHelpers.GetNullableUnderlyingType(type);
            }
 
            if (type.TypeKind == TypeKind.Enum)
            {
                AddEnumConstantValue((INamedTypeSymbol)type, constantValue, preferNumericValueOrExpandedFlagsForEnum);
            }
            else
            {
                AddLiteralValue(type.SpecialType, constantValue);
            }
        }
 
        private void AddEnumConstantValue(INamedTypeSymbol enumType, object constantValue, bool preferNumericValueOrExpandedFlags)
        {
            Debug.Assert(enumType.EnumUnderlyingType is not null);
 
            // Code copied from System.Enum            
            var isFlagsEnum = IsFlagsEnum(enumType);
            if (isFlagsEnum)
            {
                AddFlagsEnumConstantValue(enumType, constantValue, preferNumericValueOrExpandedFlags);
            }
            else if (preferNumericValueOrExpandedFlags)
            {
                // This isn't a flags enum, so just add the numeric value.
                AddLiteralValue(enumType.EnumUnderlyingType.SpecialType, constantValue);
            }
            else
            {
                // Try to see if its one of the enum values.  If so, add that.  Otherwise, just add
                // the literal value of the enum.
                AddNonFlagsEnumConstantValue(enumType, constantValue);
            }
        }
 
        /// <summary>
        /// Check if the given type is an enum with System.FlagsAttribute.
        /// </summary>
        /// <remarks>
        /// TODO: Can/should this be done using WellKnownAttributes?
        /// </remarks>
        private static bool IsFlagsEnum(ITypeSymbol typeSymbol)
        {
            Debug.Assert(typeSymbol != null);
 
            if (typeSymbol.TypeKind != TypeKind.Enum)
            {
                return false;
            }
 
            foreach (var attribute in typeSymbol.GetAttributes())
            {
                var ctor = attribute.AttributeConstructor;
                if (ctor != null)
                {
                    var type = ctor.ContainingType;
                    if (!ctor.Parameters.Any() && type.Name == "FlagsAttribute")
                    {
                        var containingSymbol = type.ContainingSymbol;
                        if (containingSymbol.Kind == SymbolKind.Namespace &&
                            containingSymbol.Name == "System" &&
                            ((INamespaceSymbol)containingSymbol.ContainingSymbol).IsGlobalNamespace)
                        {
                            return true;
                        }
                    }
                }
            }
 
            return false;
        }
 
        private void AddFlagsEnumConstantValue(INamedTypeSymbol enumType, object constantValue, bool preferNumericValueOrExpandedFlags)
        {
            // These values are sorted by value. Don't change this.
            var allFieldsAndValues = ArrayBuilder<EnumField>.GetInstance();
            GetSortedEnumFields(enumType, allFieldsAndValues);
 
            var usedFieldsAndValues = ArrayBuilder<EnumField>.GetInstance();
            try
            {
                AddFlagsEnumConstantValue(enumType, constantValue, allFieldsAndValues, usedFieldsAndValues, preferNumericValueOrExpandedFlags);
            }
            finally
            {
                allFieldsAndValues.Free();
                usedFieldsAndValues.Free();
            }
        }
 
        private void AddFlagsEnumConstantValue(
            INamedTypeSymbol enumType, object constantValue,
            ArrayBuilder<EnumField> allFieldsAndValues,
            ArrayBuilder<EnumField> usedFieldsAndValues,
            bool preferNumericValueOrExpandedFlags)
        {
            Debug.Assert(enumType.EnumUnderlyingType is not null);
            var underlyingSpecialType = enumType.EnumUnderlyingType.SpecialType;
            var constantValueULong = underlyingSpecialType.ConvertUnderlyingValueToUInt64(constantValue);
 
            var result = constantValueULong;
 
            // We will not optimize this code further to keep it maintainable. There are some
            // boundary checks that can be applied to minimize the comparisons required. This code
            // works the same for the best/worst case. In general the number of items in an enum are
            // sufficiently small and not worth the optimization.
            if (result != 0)
            {
                foreach (EnumField fieldAndValue in allFieldsAndValues)
                {
                    var valueAtIndex = fieldAndValue.Value;
 
                    // In the case that we prefer a numeric value or expanded flags, we don't want to add the
                    // field matching this precise value because we'd rather see the constituent parts.
                    if (preferNumericValueOrExpandedFlags && valueAtIndex == constantValueULong)
                    {
                        continue;
                    }
 
                    if (valueAtIndex != 0 && (result & valueAtIndex) == valueAtIndex)
                    {
                        usedFieldsAndValues.Add(fieldAndValue);
                        result -= valueAtIndex;
                        if (result == 0) break;
                    }
                }
            }
 
            // We were able to represent this number as a bitwise or of valid flags.
            if (result == 0 && usedFieldsAndValues.Count > 0)
            {
                // We want to emit the fields in lower to higher value.  So we walk backward.
                for (int i = usedFieldsAndValues.Count - 1; i >= 0; i--)
                {
                    if (i != (usedFieldsAndValues.Count - 1))
                    {
                        AddSpace();
                        AddBitwiseOr();
                        AddSpace();
                    }
 
                    ((IFieldSymbol)usedFieldsAndValues[i].IdentityOpt!).Accept(this.NotFirstVisitor);
                }
            }
            else
            {
                // We couldn't find fields to OR together to make the value.
 
                if (preferNumericValueOrExpandedFlags)
                {
                    AddLiteralValue(underlyingSpecialType, constantValue);
                    return;
                }
 
                // If we had 0 as the value, and there's an enum value equal to 0, then use that.
                var zeroField = constantValueULong == 0
                    ? EnumField.FindValue(allFieldsAndValues, 0)
                    : default(EnumField);
                if (!zeroField.IsDefault)
                {
                    Debug.Assert(zeroField.IdentityOpt is not null);
                    ((IFieldSymbol)zeroField.IdentityOpt).Accept(this.NotFirstVisitor);
                }
                else
                {
                    // Add anything else in as a literal value.
                    AddExplicitlyCastedLiteralValue(enumType, underlyingSpecialType, constantValue);
                }
            }
        }
 
        private static void GetSortedEnumFields(
            INamedTypeSymbol enumType,
            ArrayBuilder<EnumField> enumFields)
        {
            Debug.Assert(enumType.EnumUnderlyingType is not null);
            var underlyingSpecialType = enumType.EnumUnderlyingType.SpecialType;
            foreach (var member in enumType.GetMembers())
            {
                if (member.Kind == SymbolKind.Field)
                {
                    var field = (IFieldSymbol)member;
                    if (field.HasConstantValue)
                    {
                        var enumField = new EnumField(field.Name, underlyingSpecialType.ConvertUnderlyingValueToUInt64(field.ConstantValue), field);
                        enumFields.Add(enumField);
                    }
                }
            }
 
            enumFields.Sort(EnumField.Comparer);
        }
 
        private void AddNonFlagsEnumConstantValue(INamedTypeSymbol enumType, object constantValue)
        {
            Debug.Assert(enumType.EnumUnderlyingType is not null);
            var underlyingSpecialType = enumType.EnumUnderlyingType.SpecialType;
            var constantValueULong = underlyingSpecialType.ConvertUnderlyingValueToUInt64(constantValue);
 
            var enumFields = ArrayBuilder<EnumField>.GetInstance();
            GetSortedEnumFields(enumType, enumFields);
 
            // See if there's a member with this value.  If so, then use that.
            var match = EnumField.FindValue(enumFields, constantValueULong);
            if (!match.IsDefault)
            {
                Debug.Assert(match.IdentityOpt is not null);
                ((IFieldSymbol)match.IdentityOpt).Accept(this.NotFirstVisitor);
            }
            else
            {
                // Otherwise, just add the enum as a literal.
                AddExplicitlyCastedLiteralValue(enumType, underlyingSpecialType, constantValue);
            }
            enumFields.Free();
        }
    }
}