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

Allow user-specified underlying type for enums #8329

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88ce274
Add basic enum basetype tests
IISResetMe Nov 21, 2018
06ff8a3
Add optional base type to enum parsing rule
IISResetMe Nov 21, 2018
e120158
WIP: Variable underlying types for enum definitions
IISResetMe Nov 21, 2018
f58eba8
Add support for Flags() and fix implied member literal values
IISResetMe Nov 21, 2018
c1bc80d
Fix CodeFactor issue (SA1116)
IISResetMe Nov 21, 2018
f417453
Add type-aware range check for enum member values
IISResetMe Nov 21, 2018
5d1ff8a
Reset tokenizer mode in EnumDefinitionRule
IISResetMe Nov 21, 2018
fc5a608
Update error strings, add null check for Enum member values
IISResetMe Nov 21, 2018
48ac5b3
Fix CodeFactor issue (SA1000, SA1019)
IISResetMe Nov 21, 2018
55ee223
Fix valueTooBig test (off-by-one) in DefineEnum()
IISResetMe Nov 21, 2018
661e7c7
Fix type names in enum test
IISResetMe Nov 21, 2018
84edbf9
Add more enum tests
IISResetMe Nov 22, 2018
d4486d8
Better error handling + parser errors for invalid enum underlying type
IISResetMe Nov 22, 2018
b0371ba
Optimize counter branch when defining enum member values
iSazonov Nov 22, 2018
dd6eee9
Add enum type constraint check to parser
IISResetMe Nov 22, 2018
548b6cd
Merge branch 'feature/enum-with-underlying-type' of https://github.co…
IISResetMe Nov 22, 2018
1c767a9
Update enum type constraint test
IISResetMe Nov 22, 2018
c43da74
Remove redundant enum type check from PSType
IISResetMe Nov 22, 2018
19b2770
Clean up DefineEnum()
IISResetMe Nov 22, 2018
d7a90a2
Fix CodeFactor issues (SA1116, SA1001)
IISResetMe Nov 22, 2018
ba4ccac
Add enum negative value tests
IISResetMe Nov 23, 2018
46c654a
Refactor enum underlying type check
IISResetMe Nov 23, 2018
c6b2951
Change enum type parser check to TypeCode
IISResetMe Nov 23, 2018
434bbab
Fix ReportError()/ReportIncompleteInput() argument alignment in Parser
IISResetMe Nov 23, 2018
61234b9
Revert "Fix ReportError()/ReportIncompleteInput() argument alignment …
IISResetMe Nov 24, 2018
dbb4d18
Simplify type names
IISResetMe Nov 24, 2018
23f2622
Update EnumDefinitionRule syntax description
IISResetMe Nov 27, 2018
b18213e
Fix argument indentation
IISResetMe Nov 27, 2018
052c093
Expand test for enum with negative initial value
IISResetMe Nov 27, 2018
1c8c5ad
Remove binary enum value incrementations when Flags() is present
IISResetMe Nov 27, 2018
4c882f4
Remove test for binary enum value incrementations when Flags() is pre…
IISResetMe Nov 27, 2018
6d283d3
Rename ParserStrings.EnumeratorValueTooLarge
IISResetMe Jan 12, 2019
80e119c
Add test for enum literal value too small
IISResetMe Jan 12, 2019
065e151
Fix comment + variable casing
IISResetMe Jan 13, 2019
456d30b
Merge branch 'master' into feature/enum-with-underlying-type
IISResetMe Jan 13, 2019
85c36a8
Fix variable casing for ValidUnderlyingTypeCodes
IISResetMe Jan 13, 2019
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
122 changes: 84 additions & 38 deletions src/System.Management.Automation/engine/parser/PSType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,11 +1132,52 @@ internal static List<DefineEnumHelper> Sort(List<DefineEnumHelper> defineEnumHel

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))
IISResetMe marked this conversation as resolved.
Show resolved Hide resolved
{
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(
IISResetMe marked this conversation as resolved.
Show resolved Hide resolved
typeConstraintAst.Extent,
nameof(ParserStrings.InvalidUnderlyingType),
ParserStrings.InvalidUnderlyingType,
underlyingType);
break;
}

bool valueTooBig = false;

foreach (var member in _enumDefinitionAst.Members)
{
var enumerator = (PropertyMemberAst)member;
Expand All @@ -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))
iSazonov marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}
}

Expand Down
151 changes: 100 additions & 51 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4617,75 +4617,124 @@ private Token NextTypeIdentifierToken()

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);
IISResetMe marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
var resolvedType = underlyingType.GetReflectionType();
if (resolvedType == null || !ValidUnderlyingTypeCodes.HasFlag(resolvedType.GetTypeCode()))
{
ReportError(
underlyingType.Extent,
nameof(ParserStrings.InvalidUnderlyingType),
IISResetMe marked this conversation as resolved.
Show resolved Hide resolved
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<MemberAst> members = new List<MemberAst>();
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<MemberAst> members = new List<MemberAst>();
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<AttributeAst>(), members, TypeAttributes.Enum, null);
if (customAttributes != null && customAttributes.OfType<TypeConstraintAst>().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<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;
}
finally
{
// 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);
SetTokenizerMode(oldTokenizerMode);
}

return enumDefn;
}

private MemberAst EnumMemberRule()
Expand Down
7 changes: 5 additions & 2 deletions src/System.Management.Automation/resources/ParserStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
IISResetMe marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="InterfaceNameExpected" xml:space="preserve">
<value>'{0}': Interface name expected.</value>
</data>
Expand Down
Loading