mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Allow user-specified underlying type for enums (#8329)
Fix #8028 This change adds support for specifying the underlying type for an enum: ```powershell enum MyEnum : long { A = 0x0FFFFFFFFFFFFFFF B } # or enum MyByte : byte { A = 0x01 B = 0x02 C = 0x03 D } ```
This commit is contained in:
committed by
Travis Plunk
parent
f29a04c1bd
commit
59a3696f70
@@ -1132,11 +1132,52 @@ namespace System.Management.Automation.Language
|
||||
|
||||
internal void DefineEnum()
|
||||
{
|
||||
var typeConstraintAst = _enumDefinitionAst.BaseTypes.FirstOrDefault();
|
||||
var underlyingType = typeConstraintAst == null ? typeof(int) : typeConstraintAst.TypeName.GetReflectionType();
|
||||
|
||||
var definedEnumerators = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var enumBuilder = _moduleBuilder.DefineEnum(_typeName, Reflection.TypeAttributes.Public, typeof(int));
|
||||
var enumBuilder = _moduleBuilder.DefineEnum(_typeName, Reflection.TypeAttributes.Public, underlyingType);
|
||||
DefineCustomAttributes(enumBuilder, _enumDefinitionAst.Attributes, _parser, AttributeTargets.Enum);
|
||||
int value = 0;
|
||||
|
||||
dynamic value = 0;
|
||||
dynamic maxValue = 0;
|
||||
switch (Type.GetTypeCode(underlyingType))
|
||||
{
|
||||
case TypeCode.Byte:
|
||||
maxValue = byte.MaxValue;
|
||||
break;
|
||||
case TypeCode.Int16:
|
||||
maxValue = short.MaxValue;
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
maxValue = int.MaxValue;
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
maxValue = long.MaxValue;
|
||||
break;
|
||||
case TypeCode.SByte:
|
||||
maxValue = sbyte.MaxValue;
|
||||
break;
|
||||
case TypeCode.UInt16:
|
||||
maxValue = ushort.MaxValue;
|
||||
break;
|
||||
case TypeCode.UInt32:
|
||||
maxValue = uint.MaxValue;
|
||||
break;
|
||||
case TypeCode.UInt64:
|
||||
maxValue = ulong.MaxValue;
|
||||
break;
|
||||
default:
|
||||
_parser.ReportError(
|
||||
typeConstraintAst.Extent,
|
||||
nameof(ParserStrings.InvalidUnderlyingType),
|
||||
ParserStrings.InvalidUnderlyingType,
|
||||
underlyingType);
|
||||
break;
|
||||
}
|
||||
|
||||
bool valueTooBig = false;
|
||||
|
||||
foreach (var member in _enumDefinitionAst.Members)
|
||||
{
|
||||
var enumerator = (PropertyMemberAst)member;
|
||||
@@ -1145,65 +1186,70 @@ namespace System.Management.Automation.Language
|
||||
object constValue;
|
||||
if (IsConstantValueVisitor.IsConstant(enumerator.InitialValue, out constValue, false, false))
|
||||
{
|
||||
if (constValue is int)
|
||||
if (!LanguagePrimitives.TryConvertTo(constValue, underlyingType, out value))
|
||||
{
|
||||
value = (int)constValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!LanguagePrimitives.TryConvertTo(constValue, out value))
|
||||
if (constValue != null &&
|
||||
LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constValue.GetType())))
|
||||
{
|
||||
if (constValue != null &&
|
||||
LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constValue.GetType())))
|
||||
{
|
||||
_parser.ReportError(enumerator.InitialValue.Extent,
|
||||
nameof(ParserStrings.EnumeratorValueTooLarge),
|
||||
ParserStrings.EnumeratorValueTooLarge);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parser.ReportError(enumerator.InitialValue.Extent,
|
||||
nameof(ParserStrings.CannotConvertValue),
|
||||
ParserStrings.CannotConvertValue,
|
||||
ToStringCodeMethods.Type(typeof(int)));
|
||||
}
|
||||
_parser.ReportError(
|
||||
enumerator.InitialValue.Extent,
|
||||
nameof(ParserStrings.EnumeratorValueOutOfBounds),
|
||||
ParserStrings.EnumeratorValueOutOfBounds,
|
||||
ToStringCodeMethods.Type(underlyingType));
|
||||
}
|
||||
else
|
||||
{
|
||||
_parser.ReportError(
|
||||
enumerator.InitialValue.Extent,
|
||||
nameof(ParserStrings.CannotConvertValue),
|
||||
ParserStrings.CannotConvertValue,
|
||||
ToStringCodeMethods.Type(underlyingType));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parser.ReportError(enumerator.InitialValue.Extent,
|
||||
_parser.ReportError(
|
||||
enumerator.InitialValue.Extent,
|
||||
nameof(ParserStrings.EnumeratorValueMustBeConstant),
|
||||
ParserStrings.EnumeratorValueMustBeConstant);
|
||||
}
|
||||
|
||||
valueTooBig = value > maxValue;
|
||||
}
|
||||
else if (valueTooBig)
|
||||
|
||||
if (valueTooBig)
|
||||
{
|
||||
_parser.ReportError(enumerator.Extent,
|
||||
nameof(ParserStrings.EnumeratorValueTooLarge),
|
||||
ParserStrings.EnumeratorValueTooLarge);
|
||||
_parser.ReportError(
|
||||
enumerator.Extent,
|
||||
nameof(ParserStrings.EnumeratorValueOutOfBounds),
|
||||
ParserStrings.EnumeratorValueOutOfBounds,
|
||||
ToStringCodeMethods.Type(underlyingType));
|
||||
}
|
||||
|
||||
if (definedEnumerators.Contains(enumerator.Name))
|
||||
{
|
||||
_parser.ReportError(enumerator.Extent,
|
||||
_parser.ReportError(
|
||||
enumerator.Extent,
|
||||
nameof(ParserStrings.MemberAlreadyDefined),
|
||||
ParserStrings.MemberAlreadyDefined,
|
||||
enumerator.Name);
|
||||
}
|
||||
else
|
||||
else if (value != null)
|
||||
{
|
||||
value = Convert.ChangeType(value, underlyingType);
|
||||
definedEnumerators.Add(enumerator.Name);
|
||||
enumBuilder.DefineLiteral(enumerator.Name, value);
|
||||
if (value < int.MaxValue)
|
||||
{
|
||||
value += 1;
|
||||
valueTooBig = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueTooBig = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (value < maxValue)
|
||||
{
|
||||
value += 1;
|
||||
valueTooBig = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueTooBig = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4617,75 +4617,124 @@ namespace System.Management.Automation.Language
|
||||
|
||||
private StatementAst EnumDefinitionRule(List<AttributeBaseAst> customAttributes, Token enumToken)
|
||||
{
|
||||
// G enum-statement:
|
||||
// G 'enum' new-lines:opt enum-name '{' enum-member-list '}'
|
||||
// G
|
||||
// G enum-name:
|
||||
// G simple-name
|
||||
// G
|
||||
// G enum-member-list:
|
||||
// G enum-member new-lines:opt
|
||||
// G enum-member-list enum-member
|
||||
//G enum-statement:
|
||||
//G 'enum' new-lines:opt enum-name '{' enum-member-list '}'
|
||||
//G 'enum' new-lines:opt enum-name ':' enum-underlying-type '{' enum-member-list '}'
|
||||
//G
|
||||
//G enum-name:
|
||||
//G simple-name
|
||||
//G
|
||||
//G enum-underlying-type:
|
||||
//G new-lines:opt valid-type-name new-lines:opt
|
||||
//G
|
||||
//G enum-member-list:
|
||||
//G enum-member new-lines:opt
|
||||
//G enum-member-list enum-member
|
||||
|
||||
const TypeCode ValidUnderlyingTypeCodes = TypeCode.Byte | TypeCode.Int16 | TypeCode.Int32 | TypeCode.Int64 | TypeCode.SByte | TypeCode.UInt16 | TypeCode.UInt32 | TypeCode.UInt64;
|
||||
|
||||
SkipNewlines();
|
||||
var name = SimpleNameRule();
|
||||
if (name == null)
|
||||
{
|
||||
ReportIncompleteInput(After(enumToken),
|
||||
ReportIncompleteInput(
|
||||
After(enumToken),
|
||||
nameof(ParserStrings.MissingNameAfterKeyword),
|
||||
ParserStrings.MissingNameAfterKeyword,
|
||||
enumToken.Text);
|
||||
return new ErrorStatementAst(enumToken.Extent);
|
||||
}
|
||||
|
||||
SkipNewlines();
|
||||
Token lCurly = NextToken();
|
||||
if (lCurly.Kind != TokenKind.LCurly)
|
||||
TypeConstraintAst underlyingTypeConstraint = null;
|
||||
var oldTokenizerMode = _tokenizer.Mode;
|
||||
try
|
||||
{
|
||||
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
|
||||
SetTokenizerMode(TokenizerMode.Signature);
|
||||
Token colonToken = PeekToken();
|
||||
if (colonToken.Kind == TokenKind.Colon)
|
||||
{
|
||||
this.SkipToken();
|
||||
SkipNewlines();
|
||||
ITypeName underlyingType;
|
||||
Token unused;
|
||||
underlyingType = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out unused);
|
||||
if (underlyingType == null)
|
||||
{
|
||||
ReportIncompleteInput(
|
||||
After(colonToken),
|
||||
nameof(ParserStrings.TypeNameExpected),
|
||||
ParserStrings.TypeNameExpected);
|
||||
}
|
||||
else
|
||||
{
|
||||
var resolvedType = underlyingType.GetReflectionType();
|
||||
if (resolvedType == null || !ValidUnderlyingTypeCodes.HasFlag(resolvedType.GetTypeCode()))
|
||||
{
|
||||
ReportError(
|
||||
underlyingType.Extent,
|
||||
nameof(ParserStrings.InvalidUnderlyingType),
|
||||
ParserStrings.InvalidUnderlyingType,
|
||||
underlyingType.Name);
|
||||
}
|
||||
underlyingTypeConstraint = new TypeConstraintAst(underlyingType.Extent, underlyingType);
|
||||
}
|
||||
}
|
||||
|
||||
UngetToken(lCurly);
|
||||
ReportIncompleteInput(After(name),
|
||||
nameof(ParserStrings.MissingTypeBody),
|
||||
ParserStrings.MissingTypeBody,
|
||||
enumToken.Kind.Text());
|
||||
return new ErrorStatementAst(ExtentOf(enumToken, name));
|
||||
SkipNewlines();
|
||||
Token lCurly = NextToken();
|
||||
if (lCurly.Kind != TokenKind.LCurly)
|
||||
{
|
||||
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
|
||||
|
||||
UngetToken(lCurly);
|
||||
ReportIncompleteInput(
|
||||
After(name),
|
||||
nameof(ParserStrings.MissingTypeBody),
|
||||
ParserStrings.MissingTypeBody,
|
||||
enumToken.Kind.Text());
|
||||
return new ErrorStatementAst(ExtentOf(enumToken, name));
|
||||
}
|
||||
|
||||
IScriptExtent lastExtent = lCurly.Extent;
|
||||
MemberAst member;
|
||||
List<MemberAst> members = new List<MemberAst>();
|
||||
while ((member = EnumMemberRule()) != null)
|
||||
{
|
||||
members.Add(member);
|
||||
lastExtent = member.Extent;
|
||||
}
|
||||
|
||||
var rCurly = NextToken();
|
||||
if (rCurly.Kind != TokenKind.RCurly)
|
||||
{
|
||||
UngetToken(rCurly);
|
||||
ReportIncompleteInput(
|
||||
After(lCurly),
|
||||
rCurly.Extent,
|
||||
nameof(ParserStrings.MissingEndCurlyBrace),
|
||||
ParserStrings.MissingEndCurlyBrace);
|
||||
}
|
||||
|
||||
var startExtent = customAttributes != null && customAttributes.Count > 0
|
||||
? customAttributes[0].Extent
|
||||
: enumToken.Extent;
|
||||
var extent = ExtentOf(startExtent, rCurly);
|
||||
var enumDefn = new TypeDefinitionAst(extent, name.Value, customAttributes == null ? null : customAttributes.OfType<AttributeAst>(), members, TypeAttributes.Enum, underlyingTypeConstraint == null ? null : new[] { underlyingTypeConstraint });
|
||||
if (customAttributes != null && customAttributes.OfType<TypeConstraintAst>().Any())
|
||||
{
|
||||
// No need to report error since there is error reported in method StatementRule
|
||||
List<Ast> nestedAsts = new List<Ast>();
|
||||
nestedAsts.AddRange(customAttributes.OfType<TypeConstraintAst>());
|
||||
nestedAsts.Add(enumDefn);
|
||||
return new ErrorStatementAst(startExtent, nestedAsts);
|
||||
}
|
||||
|
||||
return enumDefn;
|
||||
}
|
||||
|
||||
IScriptExtent lastExtent = lCurly.Extent;
|
||||
MemberAst member;
|
||||
List<MemberAst> members = new List<MemberAst>();
|
||||
while ((member = EnumMemberRule()) != null)
|
||||
finally
|
||||
{
|
||||
members.Add(member);
|
||||
lastExtent = member.Extent;
|
||||
SetTokenizerMode(oldTokenizerMode);
|
||||
}
|
||||
|
||||
var rCurly = NextToken();
|
||||
if (rCurly.Kind != TokenKind.RCurly)
|
||||
{
|
||||
UngetToken(rCurly);
|
||||
ReportIncompleteInput(After(lCurly),
|
||||
rCurly.Extent,
|
||||
nameof(ParserStrings.MissingEndCurlyBrace),
|
||||
ParserStrings.MissingEndCurlyBrace);
|
||||
}
|
||||
|
||||
var startExtent = customAttributes != null && customAttributes.Count > 0
|
||||
? customAttributes[0].Extent
|
||||
: enumToken.Extent;
|
||||
var extent = ExtentOf(startExtent, rCurly);
|
||||
var enumDefn = new TypeDefinitionAst(extent, name.Value, customAttributes == null ? null : customAttributes.OfType<AttributeAst>(), members, TypeAttributes.Enum, null);
|
||||
if (customAttributes != null && customAttributes.OfType<TypeConstraintAst>().Any())
|
||||
{
|
||||
// no need to report error since there is error reported in method StatementRule
|
||||
List<Ast> nestedAsts = new List<Ast>();
|
||||
nestedAsts.AddRange(customAttributes.OfType<TypeConstraintAst>());
|
||||
nestedAsts.Add(enumDefn);
|
||||
return new ErrorStatementAst(startExtent, nestedAsts);
|
||||
}
|
||||
|
||||
return enumDefn;
|
||||
}
|
||||
|
||||
private MemberAst EnumMemberRule()
|
||||
|
||||
@@ -1289,8 +1289,8 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent
|
||||
<data name="CycleInEnumInitializers" xml:space="preserve">
|
||||
<value>Cannot define enum because of a cycle in the initialization expressions.</value>
|
||||
</data>
|
||||
<data name="EnumeratorValueTooLarge" xml:space="preserve">
|
||||
<value>Enumerator value is too large for a System.Int.</value>
|
||||
<data name="EnumeratorValueOutOfBounds" xml:space="preserve">
|
||||
<value>Enumerator value is either too large or too small for {0}.</value>
|
||||
</data>
|
||||
<data name="EnumeratorValueMustBeConstant" xml:space="preserve">
|
||||
<value>Enumerator value must be a constant value.</value>
|
||||
@@ -1316,6 +1316,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent
|
||||
<data name="TypeNameExpected" xml:space="preserve">
|
||||
<value>Type name expected.</value>
|
||||
</data>
|
||||
<data name="InvalidUnderlyingType" xml:space="preserve">
|
||||
<value>'{0}' is not a valid underlying type for enums. Expected a builtin integral type (one of byte, sbyte, short, ushort, int, uint, long or ulong)</value>
|
||||
</data>
|
||||
<data name="InterfaceNameExpected" xml:space="preserve">
|
||||
<value>'{0}': Interface name expected.</value>
|
||||
</data>
|
||||
|
||||
@@ -70,6 +70,40 @@ Describe 'enums' -Tags "CI" {
|
||||
It 'E5 has correct value' { [E5]::e0 | Should -Be ([E5]40) }
|
||||
It 'E6 has correct value' { [E6]::e0 | Should -Be ([E6]38) }
|
||||
}
|
||||
|
||||
Context 'Enum with non-default underlying type' {
|
||||
enum EX1 : byte { A;B;C;D }
|
||||
enum EX2 : sbyte { A;B;C;D }
|
||||
enum EX3 : short { A;B;C;D }
|
||||
enum EX4 : ushort { A;B;C;D }
|
||||
enum EX5 : int { A;B;C;D }
|
||||
enum EX6 : uint { A;B;C;D }
|
||||
enum EX7 : long { A;B;C;D }
|
||||
enum EX8 : ulong { A;B;C;D }
|
||||
|
||||
It 'EX1 has the specified underlying type' { [Enum]::GetUnderlyingType([EX1]) | Should -Be ([byte]) }
|
||||
It 'EX2 has the specified underlying type' { [Enum]::GetUnderlyingType([EX2]) | Should -Be ([sbyte]) }
|
||||
It 'EX3 has the specified underlying type' { [Enum]::GetUnderlyingType([EX3]) | Should -Be ([short]) }
|
||||
It 'EX4 has the specified underlying type' { [Enum]::GetUnderlyingType([EX4]) | Should -Be ([ushort]) }
|
||||
It 'EX5 has the specified underlying type' { [Enum]::GetUnderlyingType([EX5]) | Should -Be ([int]) }
|
||||
It 'EX6 has the specified underlying type' { [Enum]::GetUnderlyingType([EX6]) | Should -Be ([uint]) }
|
||||
It 'EX7 has the specified underlying type' { [Enum]::GetUnderlyingType([EX7]) | Should -Be ([long]) }
|
||||
It 'EX8 has the specified underlying type' { [Enum]::GetUnderlyingType([EX8]) | Should -Be ([ulong]) }
|
||||
}
|
||||
|
||||
Context 'Enum with negative user-specified values' {
|
||||
enum V1 {
|
||||
A = -4
|
||||
B = [int]::MinValue
|
||||
C
|
||||
}
|
||||
|
||||
It 'Negative values are correctly assigned to members' {
|
||||
[V1]::A.value__ | Should -Be -4
|
||||
[V1]::B.value__ | Should -Be -2147483648
|
||||
[V1]::C.value__ | Should -Be -2147483647
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Basic enum errors' -Tags "CI" {
|
||||
@@ -82,9 +116,11 @@ Describe 'Basic enum errors' -Tags "CI" {
|
||||
ShouldBeParseError 'enum foo { x; x }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { X; x }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo1 { x = [foo2]::x } enum foo2 { x = [foo1]::x }' CycleInEnumInitializers,CycleInEnumInitializers 0,28 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { e = [int]::MaxValue; e2 }' EnumeratorValueTooLarge 33 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { e = [int]::MaxValue + 1 }' EnumeratorValueTooLarge 15 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { e = [int]::MaxValue; e2 }' EnumeratorValueOutOfBounds 33 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { e = [int]::MaxValue + 1 }' EnumeratorValueOutOfBounds 15 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo : byte { e = -1 }' EnumeratorValueOutOfBounds 22 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { e = $foo }' EnumeratorValueMustBeConstant 15 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { e = "hello" }' CannotConvertValue 15 -SkipAndCheckRuntimeError
|
||||
ShouldBeParseError 'enum foo { a;b;c;' MissingEndCurlyBrace 10
|
||||
ShouldBeParseError 'enum foo : string { a }' InvalidUnderlyingType 11 -SkipAndCheckRuntimeError
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user