Why do I get "type has no typeinfo" error with an enum type
Asked Answered
I

3

25

I have declared the following enum type in which I want the first member to have the ordinal value of 1 (one) rather than the usual 0 (zero):

  type
    TMyEnum = (
               meFirstValue = 1,
               meSecondValue,
               meThirdValue
              );

If I call TypeInfo(), e.g. as part of a call to GetEnumName(), I get a compiler error:

  GetEnumName(TypeInfo(TMyEnum), Ord(aValue));

ERROR: "E2134: Type 'TMyEnum' has no typeinfo"

Why is this?

I know that classes only have typeinfo if they are compiled with the $M compiler option enabled or (derive from some class which was, such as TPersistent) but I didn't think there were any special conditions for having typeinfo for enum types.

Ichang answered 14/9, 2009 at 9:40 Comment(1)
Half related: the compiler stopped with an error: Type <void> has no typeinfo. It turned out to be a procedure like Stuff(var P);Narcotic
I
24

Type information is not supported for enums where specific ordinal values are assigned that result in enum members having ordinal values that are different to those that would normally be assigned by the compiler.

If specific values are essential or desirable, "unused" enum members will have to be inserted to "pad" the enum as required. e.g (additional indentation for emphasis only):

  type
    TMyEnum = (
                meNOTUSED1,   {= 0}
               meFirstValue,  {= 1} 
               meSecondValue,
               meThirdValue
              );

A subrange can then be used to "filter" out the unused initial value:

   TValidMyEnum = meFirstValue..meThirdValue;

Although you might then wish to consider renaming the original enum type so that your subrange type may be used throughout your project.

A subrange isn't sufficient if the enum contains "gaps":

  type
    TMyEnum = (
                meNOTUSED1,   {= 0}
               meFirstValue,  {= 1} 
               meSecondValue,
               meThirdValue,
                meNOTUSED2,
               meFinalValue   {= 5}
              );

In this case there is no simply way to extend compile-time range checking to exclude the unused members, but a couple of set types will simplify the business of implementing any necessary runtime checks:

  type
    TMyEnums = set of TMyEnum;

  const
    meNOTUSED      = [meUNUSED1, meUNUSED2]; //  .. etc as required
    meValidValues  = [Low(TMyEnum)..High(TMyEnum)] - meNOTUSED;


  if NOT (aValue in meValidValues) then
     // etc
Ichang answered 14/9, 2009 at 9:42 Comment(6)
Maybe you can alleviate the pain of doing this by using a subrange type: type TMyEnumWithDummy = ( meNOTUSED, meFirstValue, meSecondValue, meThirdValue ); TMyEnum = Succ(meNOTUSED)..High(TMyEnumWithDummy);Beanery
Yes indeed, although if you have "gaps" in the enum then a simple subrange won't be enough. In the case where I came across this I had gaps - Unfortunately I over-simplified for the initial "question". But I shall update the answer with your suggestion too.Ichang
And what about TDirection = (Negative = -1, None = 0, Positive = 1)? Is there any suggestion about how to accommodate this enum so that the compiler can generate a proper type info? Note: TDirection in the way above is very comfortable for me because I just have to multiply Ord(TDirection.<SomeMember>) by some operand to get the right result in some calculation in my project.Klapp
@Klapp Enums with negative values fall into the same category as non-contiguous or non-zero based enums: These are not values that the compiler would normally assign and so no type-info is generated for these. Sets or other validation alternatives to compile-time enforcement whilst preserving "normal" compiler-like enum values don't help here. If you want negative values in an enum then for now you simply have to give up type-info (unless/until Embarcadero address this constraint. Of course, they may have already. What version of Delphi have you tried with most recently ?).Ichang
Thanks @Deltics, I am using the last version, i.e. Delphi 10.2.3 (Tokyo) and it doesn't generate type-info for that kind of enumeration so I had to change TDirection definition to work with the default behavior, i.e. TDirection = (Negative, None, Positive) then implement an utility function to convert a TDirection value to the real value, i.e. TDirection.Negative => -1, TDirection.None => 0 and TDirection.Positive => 1Klapp
An run into this too, using enumerations that start at -1. What wonders me is that the buolt-in debugger is to be able to return the correct EnumName for contents variables of the enumtype that has no TypeInfo. How did they do that? Can I access somehow runtime?Trotyl
C
38

Discontiguous enumerations, and enumerations which don't start at zero, don't have typeinfo. For typeinfo to be implemented, it would need to be in a different format from the existing tkEnumeration, owing to backward compatibility issues.

I considered implementing a tkDiscontiguousEnumeration (or possibly better named member) for Delphi 2010, but the benefit seemed small considering their relative scarcity and the difficulties in enumeration - how do you encode the ranges efficiently? Some encodings are better for some scenarios, worse for others.

Charlottetown answered 14/9, 2009 at 10:0 Comment(1)
That's some useful and interesting background info. You might at least have considered updating the documentation for the E2134 error. This provides one example of where typeinfo is not produced but does not give any clues as to these considerations for enum types. Then again, it's taken almost 15 years of solid Delphi'ing for me to stumble over this, so as you say it's not exactly a common problem. :)Ichang
I
24

Type information is not supported for enums where specific ordinal values are assigned that result in enum members having ordinal values that are different to those that would normally be assigned by the compiler.

If specific values are essential or desirable, "unused" enum members will have to be inserted to "pad" the enum as required. e.g (additional indentation for emphasis only):

  type
    TMyEnum = (
                meNOTUSED1,   {= 0}
               meFirstValue,  {= 1} 
               meSecondValue,
               meThirdValue
              );

A subrange can then be used to "filter" out the unused initial value:

   TValidMyEnum = meFirstValue..meThirdValue;

Although you might then wish to consider renaming the original enum type so that your subrange type may be used throughout your project.

A subrange isn't sufficient if the enum contains "gaps":

  type
    TMyEnum = (
                meNOTUSED1,   {= 0}
               meFirstValue,  {= 1} 
               meSecondValue,
               meThirdValue,
                meNOTUSED2,
               meFinalValue   {= 5}
              );

In this case there is no simply way to extend compile-time range checking to exclude the unused members, but a couple of set types will simplify the business of implementing any necessary runtime checks:

  type
    TMyEnums = set of TMyEnum;

  const
    meNOTUSED      = [meUNUSED1, meUNUSED2]; //  .. etc as required
    meValidValues  = [Low(TMyEnum)..High(TMyEnum)] - meNOTUSED;


  if NOT (aValue in meValidValues) then
     // etc
Ichang answered 14/9, 2009 at 9:42 Comment(6)
Maybe you can alleviate the pain of doing this by using a subrange type: type TMyEnumWithDummy = ( meNOTUSED, meFirstValue, meSecondValue, meThirdValue ); TMyEnum = Succ(meNOTUSED)..High(TMyEnumWithDummy);Beanery
Yes indeed, although if you have "gaps" in the enum then a simple subrange won't be enough. In the case where I came across this I had gaps - Unfortunately I over-simplified for the initial "question". But I shall update the answer with your suggestion too.Ichang
And what about TDirection = (Negative = -1, None = 0, Positive = 1)? Is there any suggestion about how to accommodate this enum so that the compiler can generate a proper type info? Note: TDirection in the way above is very comfortable for me because I just have to multiply Ord(TDirection.<SomeMember>) by some operand to get the right result in some calculation in my project.Klapp
@Klapp Enums with negative values fall into the same category as non-contiguous or non-zero based enums: These are not values that the compiler would normally assign and so no type-info is generated for these. Sets or other validation alternatives to compile-time enforcement whilst preserving "normal" compiler-like enum values don't help here. If you want negative values in an enum then for now you simply have to give up type-info (unless/until Embarcadero address this constraint. Of course, they may have already. What version of Delphi have you tried with most recently ?).Ichang
Thanks @Deltics, I am using the last version, i.e. Delphi 10.2.3 (Tokyo) and it doesn't generate type-info for that kind of enumeration so I had to change TDirection definition to work with the default behavior, i.e. TDirection = (Negative, None, Positive) then implement an utility function to convert a TDirection value to the real value, i.e. TDirection.Negative => -1, TDirection.None => 0 and TDirection.Positive => 1Klapp
An run into this too, using enumerations that start at -1. What wonders me is that the buolt-in debugger is to be able to return the correct EnumName for contents variables of the enumtype that has no TypeInfo. How did they do that? Can I access somehow runtime?Trotyl
E
1

When you want to convert enums into specific values (and back) I useally create an array const, with the desired values per enum value:

Const MyEnumValues: array[TMyEnum] of integer = (1,2,5);

This way when the enum gets expanded you get an compiler error stating you are missing an array value.

Please note when changing the order of the enums, you must change the values accordingly.

To get the ‘value’ for an enum values just write:

Value := MyEnumValues[myenum];

And to get the enum value based on the ‘value’ just loop though the values of MyEnumValues:

Function GetEnumByValue(value:integer): TMyEnum;
Var
  myenum: TMyEnum;
Begin
  For myenum = low(TMyEnum) to high(TMyEnum) do
    If MyEnumValues[myenum] = value then
      exit(myenum);
  Raise exception.create(‘invalid value for tmyenum’);
End;
Ellie answered 17/9, 2019 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.