Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite Enum and add {ISpanFormattable}.TryFormat #78580

Merged
merged 15 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand All @@ -20,6 +20,15 @@ public abstract partial class Enum
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe CorElementType InternalGetCorElementType(MethodTable* pMT);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt)
{
Debug.Assert(rt.IsActualEnum);
CorElementType elementType = InternalGetCorElementType((MethodTable*)rt.GetUnderlyingNativeHandle());
GC.KeepAlive(rt);
return elementType;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe CorElementType InternalGetCorElementType()
{
Expand Down Expand Up @@ -73,26 +82,31 @@ internal static unsafe RuntimeType InternalGetUnderlyingType(RuntimeType enumTyp
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static EnumInfo GetEnumInfo(RuntimeType enumType, bool getNames = true)
private static EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(RuntimeType enumType, bool getNames = true)
where TUnderlyingValue : struct, INumber<TUnderlyingValue>
{
return enumType.GenericCache is EnumInfo info && (!getNames || info.Names is not null) ?
return enumType.GenericCache is EnumInfo<TUnderlyingValue> info && (!getNames || info.Names is not null) ?
info :
InitializeEnumInfo(enumType, getNames);

[MethodImpl(MethodImplOptions.NoInlining)]
static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames)
static EnumInfo<TUnderlyingValue> InitializeEnumInfo(RuntimeType enumType, bool getNames)
{
ulong[]? values = null;
TUnderlyingValue[]? values = null;
string[]? names = null;
RuntimeTypeHandle enumTypeHandle = enumType.TypeHandle;

GetEnumValuesAndNames(
new QCallTypeHandle(ref enumTypeHandle),
new QCallTypeHandle(ref enumType),
ObjectHandleOnStack.Create(ref values),
ObjectHandleOnStack.Create(ref names),
getNames ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);

Debug.Assert(values!.GetType() == typeof(TUnderlyingValue[]));
Debug.Assert(!getNames || names!.GetType() == typeof(string[]));

bool hasFlagsAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false);

var entry = new EnumInfo(hasFlagsAttribute, values!, names!);
var entry = new EnumInfo<TUnderlyingValue>(hasFlagsAttribute, values, names!);
enumType.GenericCache = entry;
return entry;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,10 @@
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Reflection.EnumInfo</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Reflection.EnumInfo`1</Target>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:System.Reflection.Runtime.General.MetadataReaderExtensions</Target>
Expand Down Expand Up @@ -972,4 +976,4 @@
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant``1(System.IntPtr)-&gt;T?:[T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute]</Target>
</Suppression>
</Suppressions>
</Suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;

using EETypeElementType = Internal.Runtime.EETypeElementType;

Expand Down Expand Up @@ -164,7 +165,8 @@ public abstract object ActivatorCreateInstance(

public abstract Assembly[] GetLoadedAssemblies();

public abstract EnumInfo GetEnumInfo(Type type);
public abstract EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(Type type)
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
where TUnderlyingValue : struct, INumber<TUnderlyingValue>;

public abstract DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection.Runtime.General;
using System.Reflection.Runtime.TypeInfos;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -97,7 +98,9 @@ public abstract class ExecutionEnvironment
// Other
//==============================================================================================
public abstract FieldAccessor CreateLiteralFieldAccessor(object value, RuntimeTypeHandle fieldTypeHandle);
public abstract EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle);
public abstract EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(RuntimeTypeHandle typeHandle)
where TUnderlyingValue : struct, INumber<TUnderlyingValue>;

public abstract IntPtr GetDynamicInvokeThunk(MethodInvoker invoker);

//==============================================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using Internal.Runtime.Augments;
using Internal.Runtime.CompilerServices;
using Internal.Reflection.Augments;

Expand All @@ -23,7 +25,29 @@ internal static EnumInfo GetEnumInfo(Type enumType, bool getNames = true)
Debug.Assert(enumType is RuntimeType);
Debug.Assert(enumType.IsEnum);

return ReflectionAugments.ReflectionCoreCallbacks.GetEnumInfo(enumType);
RuntimeType rt = (RuntimeType)enumType;
return Type.GetTypeCode(RuntimeAugments.GetEnumUnderlyingType(rt.TypeHandle)) switch
{
TypeCode.SByte => GetEnumInfo<sbyte>(rt),
TypeCode.Byte => GetEnumInfo<byte>(rt),
TypeCode.Int16 => GetEnumInfo<short>(rt),
TypeCode.UInt16 => GetEnumInfo<ushort>(rt),
TypeCode.Int32 => GetEnumInfo<int>(rt),
TypeCode.UInt32 => GetEnumInfo<uint>(rt),
TypeCode.Int64 => GetEnumInfo<long>(rt),
TypeCode.UInt64 => GetEnumInfo<ulong>(rt),
_ => throw new NotSupportedException(),
};
}

internal static EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(Type enumType, bool getNames = true)
where TUnderlyingValue : struct, INumber<TUnderlyingValue>
{
Debug.Assert(enumType != null);
Debug.Assert(enumType is RuntimeType);
Debug.Assert(enumType.IsEnum);

return ReflectionAugments.ReflectionCoreCallbacks.GetEnumInfo<TUnderlyingValue>(enumType);
}
#pragma warning restore

Expand All @@ -32,6 +56,12 @@ private static object InternalBoxEnum(Type enumType, long value)
return ToObject(enumType.TypeHandle.ToEETypePtr(), value);
}

private static CorElementType InternalGetCorElementType(RuntimeType rt)
{
Debug.Assert(rt.IsActualEnum);
return rt.TypeHandle.ToEETypePtr().CorElementType;
}

private CorElementType InternalGetCorElementType()
{
return this.GetEETypePtr().CorElementType;
Expand Down Expand Up @@ -115,14 +145,6 @@ internal static Type InternalGetUnderlyingType(RuntimeType enumType)
return GetEnumInfo(enumType).UnderlyingType;
}

public static TEnum[] GetValues<TEnum>() where TEnum : struct, Enum
{
Array values = GetEnumInfo(typeof(TEnum)).ValuesAsUnderlyingType;
TEnum[] result = new TEnum[values.Length];
Array.Copy(values, result, values.Length);
return result;
}

//
// Checks if value.GetType() matches enumType exactly.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,46 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime;
using System.Runtime.CompilerServices;

namespace System.Reflection
{
[ReflectionBlocked]
public sealed class EnumInfo
public abstract class EnumInfo
{
public EnumInfo(Type underlyingType, object[] rawValues, string[] names, bool isFlags)
private protected EnumInfo(Type underlyingType, string[] names, bool isFlags)
{
Debug.Assert(rawValues.Length == names.Length);

UnderlyingType = underlyingType;

int numValues = rawValues.Length;
ulong[] values = new ulong[numValues];
for (int i = 0; i < numValues; i++)
{
object rawValue = rawValues[i];

ulong rawUnboxedValue;
if (rawValue is ulong)
{
rawUnboxedValue = (ulong)rawValue;
}
else
{
// This conversion is this way for compatibility: do a value-preseving cast to long - then store (and compare) as ulong. This affects
// the order in which the Enum apis return names and values.
rawUnboxedValue = (ulong)(((IConvertible)rawValue).ToInt64(null));
}
values[i] = rawUnboxedValue;
}

// Need to sort the `names` and `rawValues` arrays according to the `values` array
ulong[] valuesCopy = (ulong[])values.Clone();
Array.Sort(keys: valuesCopy, items: rawValues, comparer: Comparer<ulong>.Default);
Array.Sort(keys: values, items: names, comparer: Comparer<ulong>.Default);

Names = names;
Values = values;

// Create the unboxed version of values for the Values property to return. (We didn't do this earlier because
// declaring "rawValues" as "Array" would prevent us from using the generic overload of Array.Sort()).
//
// The array element type is the underlying type, not the enum type. (The enum type could be an open generic.)
ValuesAsUnderlyingType = Type.GetTypeCode(UnderlyingType) switch
{
TypeCode.Byte => new byte[numValues],
TypeCode.SByte => new sbyte[numValues],
TypeCode.UInt16 => new ushort[numValues],
TypeCode.Int16 => new short[numValues],
TypeCode.UInt32 => new uint[numValues],
TypeCode.Int32 => new int[numValues],
TypeCode.UInt64 => new ulong[numValues],
TypeCode.Int64 => new long[numValues],
_ => throw new NotSupportedException(),
};
Array.Copy(rawValues, ValuesAsUnderlyingType, numValues);

HasFlagsAttribute = isFlags;

ValuesAreSequentialFromZero = true;
for (int i = 0; i < values.Length; i++)
{
if (values[i] != (ulong)i)
{
ValuesAreSequentialFromZero = false;
break;
}
}
}

internal Type UnderlyingType { get; }
internal string[] Names { get; }
internal ulong[] Values { get; }
internal Array ValuesAsUnderlyingType { get; }
internal bool HasFlagsAttribute { get; }
}

[ReflectionBlocked]
public sealed class EnumInfo<TUnderlyingValue> : EnumInfo
where TUnderlyingValue : struct, INumber<TUnderlyingValue>
{
public EnumInfo(Type underlyingType, TUnderlyingValue[] values, string[] names, bool isFlags) :
base(underlyingType, names, isFlags)
{
Debug.Assert(values.Length == names.Length);

Array.Sort(keys: values, items: names);

Values = values;
ValuesAreSequentialFromZero = Enum.AreSequentialFromZero(values);
}

internal TUnderlyingValue[] Values { get; }
internal bool ValuesAreSequentialFromZero { get; }

public TUnderlyingValue[] CloneValues() =>
new ReadOnlySpan<TUnderlyingValue>(Values).ToArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -404,15 +404,15 @@ public sealed override void MakeTypedReference(object target, FieldInfo[] flds,

public sealed override Assembly[] GetLoadedAssemblies() => RuntimeAssemblyInfo.GetLoadedAssemblies();

public sealed override EnumInfo GetEnumInfo(Type type)
public sealed override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(Type type)
{
RuntimeTypeInfo runtimeType = type.CastToRuntimeTypeInfo();

EnumInfo? info = runtimeType.GenericCache as EnumInfo;
EnumInfo<TUnderlyingValue>? info = runtimeType.GenericCache as EnumInfo<TUnderlyingValue>;
if (info != null)
return info;

info = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo(runtimeType.TypeHandle);
info = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo<TUnderlyingValue>(runtimeType.TypeHandle);
runtimeType.GenericCache = info;
return info;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ public abstract class RuntimeType : TypeInfo
// Do a value-preserving cast of both it and the enum values and do a 64-bit compare.

if (!IsActualEnum)
throw new ArgumentException(SR.Arg_MustBeEnum);
throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");

return Enum.GetEnumName(this, rawValue);
return Enum.GetName(this, rawValue);
}

public sealed override string[] GetEnumNames()
{
if (!IsActualEnum)
throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");

string[] ret = Enum.InternalGetNames(this);
string[] ret = Enum.GetNamesNoCopy(this);

// Make a copy since we can't hand out the same array since users can modify them
return new ReadOnlySpan<string>(ret).ToArray();
Expand Down Expand Up @@ -87,7 +87,7 @@ public sealed override bool IsEnumDefined(object value)
throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, value.GetType(), underlyingType));
}

return Enum.GetEnumName(this, rawValue) != null;
return Enum.GetName(this, rawValue) != null;
}
}

Expand All @@ -97,16 +97,16 @@ public sealed override Array GetEnumValues()
if (!IsActualEnum)
throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");

Array values = Enum.GetEnumInfo(this).ValuesAsUnderlyingType;
Array values = Enum.GetValuesAsUnderlyingTypeNoCopy(this);
int count = values.Length;

// Without universal shared generics, chances are slim that we'll have the appropriate
// array type available. Offer an escape hatch that avoids a missing metadata exception
// at the cost of a small appcompat risk.
Array result;
if (AppContext.TryGetSwitch("Switch.System.Enum.RelaxedGetValues", out bool isRelaxed) && isRelaxed)
result = Array.CreateInstance(Enum.InternalGetUnderlyingType(this), count);
else
result = Array.CreateInstance(this, count);
Array result = AppContext.TryGetSwitch("Switch.System.Enum.RelaxedGetValues", out bool isRelaxed) && isRelaxed ?
Array.CreateInstance(Enum.InternalGetUnderlyingType(this), count) :
Array.CreateInstance(this, count);

Array.Copy(values, result, values.Length);
return result;
}
Expand All @@ -116,7 +116,7 @@ public sealed override Array GetEnumValuesAsUnderlyingType()
if (!IsActualEnum)
throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");

return (Array)Enum.GetEnumInfo(this).ValuesAsUnderlyingType.Clone();
return (Array)Enum.GetValuesAsUnderlyingTypeNoCopy(this).Clone();
}

internal bool IsActualEnum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ namespace Internal.Reflection
{
internal class ReflectionCoreCallbacksImplementation : ReflectionCoreCallbacks
{
public override EnumInfo GetEnumInfo(Type type)
{
return new EnumInfo(
public override EnumInfo<TUnderlyingValue> GetEnumInfo<TUnderlyingValue>(Type type) =>
new EnumInfo<TUnderlyingValue>(
RuntimeAugments.GetEnumUnderlyingType(type.TypeHandle),
rawValues: Array.Empty<object>(),
values: Array.Empty<TUnderlyingValue>(),
names: Array.Empty<string>(),
isFlags: false);
}

public override DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type)
=> throw new NotSupportedException(SR.Reflection_Disabled);
Expand Down
Loading