File: Shared\Utilities\EnumValueUtilities.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Utilities
{
    internal static class EnumValueUtilities
    {
        /// <summary>
        /// Determines, using heuristics, what the next likely value is in this enum.
        /// </summary>
        public static object GetNextEnumValue(INamedTypeSymbol enumType)
        {
            var orderedExistingConstants = enumType.GetMembers()
                                            .OfType<IFieldSymbol>()
                                            .Where(f => f.HasConstantValue)
                                            .Select(f => f.ConstantValue)
                                            .OfType<IComparable>()
                                            .OrderByDescending(f => f).ToList();
            var existingConstants = orderedExistingConstants.ToSet();
 
            if (LooksLikeFlagsEnum(orderedExistingConstants))
            {
                if (orderedExistingConstants.Count == 0)
                {
                    return CreateOne(enumType.EnumUnderlyingType.SpecialType);
                }
                else
                {
                    var largest = orderedExistingConstants[0];
                    return Multiply(largest, 2);
                }
            }
            else if (orderedExistingConstants.Count > 0)
            {
                for (uint i = 1; i <= existingConstants.Count; i++)
                {
                    var nextValue = Add(orderedExistingConstants[0], i);
                    if (!existingConstants.Contains(nextValue))
                    {
                        return nextValue;
                    }
                }
            }
 
            return null;
        }
 
        private static object CreateOne(SpecialType specialType)
            => specialType switch
            {
                SpecialType.System_SByte => (sbyte)1,
                SpecialType.System_Byte => (byte)1,
                SpecialType.System_Int16 => (short)1,
                SpecialType.System_UInt16 => (ushort)1,
                SpecialType.System_Int32 => 1,
                SpecialType.System_UInt32 => (uint)1,
                SpecialType.System_Int64 => (long)1,
                SpecialType.System_UInt64 => (ulong)1,
                _ => 1,
            };
 
        private static IComparable Multiply(IComparable value, uint number)
            => value switch
            {
                long v => unchecked(v * number),
                ulong v => unchecked(v * number),
                int v => unchecked((int)(v * number)),
                uint v => unchecked(v * number),
                short v => unchecked((short)(v * number)),
                ushort v => unchecked((ushort)(v * number)),
                sbyte v => unchecked((sbyte)(v * number)),
                byte v => unchecked((byte)(v * number)),
                _ => null,
            };
 
        private static IComparable Add(IComparable value, uint number)
            => value switch
            {
                long v => unchecked(v + number),
                ulong v => unchecked(v + number),
                int v => unchecked((int)(v + number)),
                uint v => unchecked(v + number),
                short v => unchecked((short)(v + number)),
                ushort v => unchecked((ushort)(v + number)),
                sbyte v => unchecked((sbyte)(v + number)),
                byte v => unchecked((byte)(v + number)),
                _ => null,
            };
 
        private static bool GreaterThanOrEqualsZero(IComparable value)
            => value switch
            {
                long v => v >= 0,
                ulong v => v >= 0,
                int v => v >= 0,
                uint v => v >= 0,
                short v => v >= 0,
                ushort v => v >= 0,
                sbyte v => v >= 0,
                byte v => v >= 0,
                _ => false,
            };
 
        private static bool LooksLikeFlagsEnum(List<IComparable> existingConstants)
        {
            if (existingConstants.Count >= 1 &&
               IntegerUtilities.HasOneBitSet(existingConstants[0]) &&
               Multiply(existingConstants[0], 2).CompareTo(existingConstants[0]) > 0 &&
               existingConstants.All(GreaterThanOrEqualsZero))
            {
                if (existingConstants.Count == 1)
                {
                    return true;
                }
 
                if (existingConstants[0].Equals(Multiply(existingConstants[1], 2)))
                {
                    // If you only have two values, and they're 1 and 2, then don't assume this is a
                    // flags enum.  The person could have been trying to type, 1, 2, 3 instead.
                    if (existingConstants[0].Equals(Convert.ChangeType(2, existingConstants[0].GetType())) &&
                        existingConstants[1].Equals(Convert.ChangeType(1, existingConstants[1].GetType())))
                    {
                        return false;
                    }
 
                    return true;
                }
            }
 
            return false;
        }
    }
}