diff --git a/src/System.Management.Automation/engine/parser/PSType.cs b/src/System.Management.Automation/engine/parser/PSType.cs index 0c26916e84d..f3634c7dffd 100644 --- a/src/System.Management.Automation/engine/parser/PSType.cs +++ b/src/System.Management.Automation/engine/parser/PSType.cs @@ -1132,11 +1132,52 @@ internal static List Sort(List defineEnumHel internal void DefineEnum() { + var typeConstraintAst = _enumDefinitionAst.BaseTypes.FirstOrDefault(); + var underlyingType = typeConstraintAst == null ? typeof(int) : typeConstraintAst.TypeName.GetReflectionType(); + var definedEnumerators = new HashSet(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 @@ internal void DefineEnum() 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; } } diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 64ab0266572..7af775f56c5 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -4617,75 +4617,124 @@ private Token NextTypeIdentifierToken() private StatementAst EnumDefinitionRule(List 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. - IScriptExtent lastExtent = lCurly.Extent; - MemberAst member; - List members = new List(); - while ((member = EnumMemberRule()) != null) - { - members.Add(member); - lastExtent = member.Extent; - } + UngetToken(lCurly); + ReportIncompleteInput( + After(name), + nameof(ParserStrings.MissingTypeBody), + ParserStrings.MissingTypeBody, + enumToken.Kind.Text()); + return new ErrorStatementAst(ExtentOf(enumToken, name)); + } - var rCurly = NextToken(); - if (rCurly.Kind != TokenKind.RCurly) - { - UngetToken(rCurly); - ReportIncompleteInput(After(lCurly), - rCurly.Extent, - nameof(ParserStrings.MissingEndCurlyBrace), - ParserStrings.MissingEndCurlyBrace); - } + IScriptExtent lastExtent = lCurly.Extent; + MemberAst member; + List members = new List(); + while ((member = EnumMemberRule()) != null) + { + members.Add(member); + lastExtent = member.Extent; + } - 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(), members, TypeAttributes.Enum, null); - if (customAttributes != null && customAttributes.OfType().Any()) + 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(), members, TypeAttributes.Enum, underlyingTypeConstraint == null ? null : new[] { underlyingTypeConstraint }); + if (customAttributes != null && customAttributes.OfType().Any()) + { + // No need to report error since there is error reported in method StatementRule + List nestedAsts = new List(); + nestedAsts.AddRange(customAttributes.OfType()); + nestedAsts.Add(enumDefn); + return new ErrorStatementAst(startExtent, nestedAsts); + } + + return enumDefn; + } + finally { - // no need to report error since there is error reported in method StatementRule - List nestedAsts = new List(); - nestedAsts.AddRange(customAttributes.OfType()); - nestedAsts.Add(enumDefn); - return new ErrorStatementAst(startExtent, nestedAsts); + SetTokenizerMode(oldTokenizerMode); } - - return enumDefn; } private MemberAst EnumMemberRule() diff --git a/src/System.Management.Automation/resources/ParserStrings.resx b/src/System.Management.Automation/resources/ParserStrings.resx index 6cd368daffd..20d462d9bed 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -1289,8 +1289,8 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Cannot define enum because of a cycle in the initialization expressions. - - Enumerator value is too large for a System.Int. + + Enumerator value is either too large or too small for {0}. Enumerator value must be a constant value. @@ -1316,6 +1316,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Type name expected. + + '{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) + '{0}': Interface name expected. diff --git a/test/powershell/Language/Classes/scripting.enums.tests.ps1 b/test/powershell/Language/Classes/scripting.enums.tests.ps1 index 02bb420e1e3..a3a86863a0a 100644 --- a/test/powershell/Language/Classes/scripting.enums.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.enums.tests.ps1 @@ -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 }