// 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 System.Runtime.CompilerServices; 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; } public static TToEnum ConvertEnum<TFromEnum, TToEnum>(TFromEnum value) where TFromEnum : struct, Enum where TToEnum : struct, Enum { // Ensure that this is only called for enums that are actually compatible with each other. Contract.ThrowIfTrue(typeof(TFromEnum).GetEnumUnderlyingType() != typeof(TToEnum).GetEnumUnderlyingType()); return Unsafe.As<TFromEnum, TToEnum>(ref value); } } |