Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2022-08

Gaps in a non-contiguous enum

12 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Juha Manninen

I was able to fix issue
https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/39832 about TEnumPropertyEditor in Lazarus IdeIntf.
Explanation:
function GetEnumName() in FPC's rtl/objpas/typinfo.pp returns the enum's unit name for one gap in a non-contiguous enum, and an empty string for the other gaps. IMO returning the unit name can be considered a bug in FPC code. Fortunately TPropertyEditor has a method GetPropTypeUnitName(), made by Mattias I guess, which I could use to filter out the unit name. An enum element cannot have the same name as the unit which makes this a robust solution.
This is the fixed property editor method:

procedure TEnumPropertyEditor.GetValues(Proc: TGetStrProc);
var
  I: Integer;
  EnumType: PTypeInfo;
  s, EnumUnitName: String;
begin
  EnumType := GetPropType;
  EnumUnitName := GetPropTypeUnitName;
  with GetTypeData(EnumType)^ do
    for I := MinValue to MaxValue do begin
      s := GetEnumName(EnumType, I);
      // An empty string and the enum's unit name happen in gaps
      // of a non-contiguous enum. Why the unit name? A bug in FPC code?
      if (s  '') and (s  EnumUnitName) then
        Proc(s);
    end;
end;

Property editors deal with RTTI.
I started to think about how such enum elements are iterated in a normal user code which doesn't need RTTI.
I came up with this, using the Fruit example enum from the bug report:

program enumconsole;
type
  //TFruit = (fApple, fOrange, fBanana, fGrapes, fPear);
  TFruitMore = (fmApple=0, fmOrange=2, fmBanana=4, fmGrapes, fmPear);
const
  CFruitMoreSet = [fmApple, fmOrange, fmBanana, fmGrapes, fmPear];
var
  e: TFruitMore;
begin
  for e := Low(TFruitMore) to High(TFruitMore) do
    if e in CFruitMoreSet then
      WriteLn(Ord(e), ': ', e);
end.

It seems there is no easy way, or at least I didn't find any. The code requires a constant set with all enum elements included. Feels like a hack.
Leaving out the test "if e in CFruitMoreSet then" leads to a run time error. If there is a better way to iterate, please tell me.

Regards,
Juha

Reply by email

Martin Frb
to Juha Manninen
Juha Manninen wrote 6 lines
      // An empty string and the enum's unit name happen in gaps       // of a non-contiguous enum. Why the unit name? A bug in FPC code?   with GetTypeData(EnumType)^ do     for I := MinValue to MaxValue do begin       s := GetEnumName(EnumType, I);

Because you are not supposed to use MaxValue
Instead use  GetEnumNameCount

     for I := MinValue to MinValue + GetEnumNameCount(...) - 1 do begin

The unit name is returned because the TypeDate stores the unitname after the continuous list of element names. And once you are out of index ...

Reply by email

Juha Manninen
to Martin Frb

On Thu, Aug 11, 2022 at 4:44 PM Martin Frb via fpc-devel <

Show quoted text 9 lines
wrote: Because you are not supposed to use MaxValue Instead use GetEnumNameCount for I := MinValue to MinValue + GetEnumNameCount(...) - 1 do begin The unit name is returned because the TypeDate stores the unitname after the continuous list of element names. And once you are out of index ...

Damn, you are right. I fixed it better.
The MaxValue is used in many places for enum and set properties while GetEnumNameCount() is not used anywhere.
There are other similar bugs. At least these 2 in propedits.pp look fishy:
- procedure TSetPropertyEditor.GetProperties
- subfunction IsPropInSet under function IsInteresting

OK, it was not a bug in FPC code. Sorry.

Regards,
Juha

Reply by email

Martin Frb
to Juha Manninen
Juha Manninen wrote
The MaxValue is used in many places for enum and set properties while GetEnumNameCount() is not used anywhere. There are other similar bugs. At least these 2 in propedits.pp look fishy: - procedure TSetPropertyEditor.GetProperties

CompType of a set is an enum, so yes => GetEnumNameCount

- subfunction IsPropInSet under function IsInteresting

seems the same

OK, it was not a bug in FPC code. Sorry.

fpc code is still "unexpected".

It takes into account the MinValue

type TMyEnum = (a=5, b=7);

GetEnumName(pt, 5) // a
GetEnumName(pt, 6) // b // should be 7

if it is just the nth name, then it should start at 0 (or if needs be at 1).

Reply by email

Juha Manninen
to Martin Frb

On Thu, Aug 11, 2022 at 9:37 PM Martin Frb via fpc-devel <

Show quoted text 13 lines
wrote: fpc code is still "unexpected". It takes into account the MinValue type TMyEnum = (a=5, b=7); GetEnumName(pt, 5) // a GetEnumName(pt, 6) // b // should be 7 if it is just the nth name, then it should start at 0 (or if needs be at 1).

And function GetEnumValue() is buggy.
This was noticed when testing the original Lazarus enum property editor bug. A little modified enum :

type
  TFruitMore = (fmApple=1, fmOrange=3, fmBanana=5, fmGrapes, fmPear);
var
  I: Integer;
  EnumType: PTypeInfo;
...
  I := GetEnumValue(EnumType, 'fmBanana');  // Returns 3
  I := GetEnumValue(EnumType, 'fmGrapes');  // Returns 4 which is an
illegal value
Looking at the source of GetEnumValue() the reason is clear.
It increments Count one by one while it should take the gaps into account.
---
     ;
     While (Result=-1) and (PByte(PS)^0) do
       begin
         If ShortCompareText(PS^, sName) = 0 then
           Result:=Count+PT^.MinValue;
         PS:=PShortString(pointer(PS)+PByte(PS)^+1);
         Inc(Count);
       end;
---
Where are the ordinal values for enums stored? They are not in TTypeData.
I wanted to fix this right away but I have no idea how.

Regards,
Juha

Reply by email

Michael Van Canneyt
to Juha Manninen
Juha Manninen wrote 16 lines
On Thu, Aug 11, 2022 at 9:37 PM Martin Frb via fpc-devel < > wrote: > fpc code is still "unexpected". > It takes into account the MinValue > > type TMyEnum = (a=5, b=7); > > GetEnumName(pt, 5) // a > GetEnumName(pt, 6) // b // should be 7 > > if it is just the nth name, then it should start at 0 (or if needs be at > 1). > And function GetEnumValue() is buggy.

It is not buggy.
It does not support enumerateds with gaps, because RTTI does not support enumerateds with gaps. It never has.

So if something needs fixing, it's FPC that needs to give an error when you try to get RTTI for an enum with gaps, and amend the documentation for GetEnumValue and GetEnumName.

This lack of RTTI info is completely Delphi compatible, by the way.

The following program prints 'Property info is null' ---
program rttitest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
System.SysUtils, System.Classes,

   TypInfo;
type
   TFruitMore = (fmApple=1, fmOrange=3, fmBanana=5, fmGrapes, fmPear);
   TMyClass = class(TComponent)
   private
     FFruit: TFruitMore;
   Published
     property Fruit : TFruitMore Read FFruit Write FFruit;
   end;
var
   I: Integer;
   PI : PPropInfo;
   EnumType: PTypeInfo;
   aClass : TMyClass;
begin
   aClass:=TMyClass.Create(Nil);
   PI:=GetPropInfo(aClass,'Fruit',tkAny);
   if PI=nil then
     Writeln('Property info is null')
   else if PI^.PropType=nil then
     Writeln('Type info is null')
   else
     begin
     EnumType:=PI^.PropType^;
     I := GetEnumValue(EnumType, 'fmBanana');  // Returns 3
     Writeln(I);
     I := GetEnumValue(EnumType, 'fmGrapes');  // Returns 4 which is an
     Writeln(I);
     end;
   Readln;
end.
---

If you change it to

   EnumType:=TypeInfo(TFruitMore);

Delphi will even write a compile-time error:

[dcc32 Error] rttitest.dpr(29): E2134 Type 'TFruitMore' has no type info

So what you want to do is simply not supported.

Gaps in enums are an ugly hack to allow importing C enums.

Don't use it in proper pascal code.

Michael.

Reply by email

Martin Frb
to Michael Van Canneyt
Michael Van Canneyt wrote 7 lines
On Tue, 16 Aug 2022, Juha Manninen via fpc-devel wrote: > And function GetEnumValue() is buggy. It is not buggy. It does not support enumerateds with gaps, because RTTI does not support enumerateds with gaps. It never has.

...

  Published     property Fruit : TFruitMore Read FFruit Write FFruit;

...

Show quoted text 5 lines
If you change it to   EnumType:=TypeInfo(TFruitMore); Delphi will even write a compile-time error:

Then maybe the property should not compile either.

What is the point to allow it being published, if then you can not get (correct) typeinfo? (and therefore not stream it)

Reply by email

Michael Van Canneyt
to Martin Frb
Martin Frb wrote 21 lines
On 16/08/2022 09:37, Michael Van Canneyt via fpc-devel wrote: > > On Tue, 16 Aug 2022, Juha Manninen via fpc-devel wrote: >> And function GetEnumValue() is buggy. > > It is not buggy. It does not support enumerateds with gaps, because > RTTI does not support enumerateds with gaps. > It never has. ... > >   Published >     property Fruit : TFruitMore Read FFruit Write FFruit; ... > If you change it to > >   EnumType:=TypeInfo(TFruitMore); > > Delphi will even write a compile-time error: Then maybe the property should not compile either.

I agree we should simply forbid it in ObjFPC mode.

What is the point to allow it being published, if then you can not get (correct) typeinfo? (and therefore not stream it)

Like you, I think there is no point...

Michael.

Reply by email

Ondrej Pokorny
to Michael Van Canneyt: It is not buggy. It does not support enumerateds with gaps, because RTTI does not support…
Michael Van Canneyt wrote 22 lines
On Tue, 16 Aug 2022, Juha Manninen via fpc-devel wrote: > On Thu, Aug 11, 2022 at 9:37 PM Martin Frb via fpc-devel < > > wrote: > >> fpc code is still "unexpected". >> It takes into account the MinValue >> >> type TMyEnum = (a=5, b=7); >> >> GetEnumName(pt, 5) // a >> GetEnumName(pt, 6) // b // should be 7 >> >> if it is just the nth name, then it should start at 0 (or if needs >> be at >> 1). >> > > And function GetEnumValue() is buggy. It is not buggy. It does not support enumerateds with gaps, because RTTI does not support enumerateds with gaps. It never has.

Well it is buggy, but a little bit differently: FPC indeed creates PropInfo for enumerated types with gaps!

Allow me to slightly edit your demo code:

program RTTITest;
{$ifdef DCC}
// Delphi
{$APPTYPE CONSOLE}
{$else}
// FPC
{$mode objfpc}{$h+}
{$endif}
uses
   SysUtils, Classes, TypInfo;
type
   TFruitMore = (fmApple=1, fmOrange=3, fmBanana=5, fmGrapes, fmPear);
   TMyClass = class(TPersistent)
   private
     FFruit: TFruitMore;
   Published
     property Fruit : TFruitMore Read FFruit Write FFruit;
   end;
var
   PI : PPropInfo;
   aClass : TMyClass;
   TypeData : PTypeData;
begin
   aClass:=TMyClass.Create;
   TypeData:=GetTypeData(aClass.ClassInfo);
   Writeln('PropCount: ', TypeData^.PropCount); // Delphi writes 0, FPC
writes 1
   PI:=GetPropInfo(aClass,'Fruit',tkAny);
   if PI=nil then
     Writeln('Property info is null')
   else
   begin
     Writeln('Property name: ', PI^.Name);
     Writeln('Property kind: ', GetEnumName(TypeInfo(TTypeKind),
Ord(PI^.PropType^.Kind)));
   end;
   Readln;
end.

--- code end

Run it in Delphi and FPC and compare the outputs:

Delphi output:
PropCount: 0
Property info is null

FPC output:
PropCount: 1
Property name: Fruit
Property kind: tkEnumeration

As you can see FPC creates a valid PropInfo for the property with valid TypeInfo (PI^.PropType) of the kind tkEnumeration! This is definitely wrong.

So the solution is either:

1.) don't create PropInfo for the Fruit property like Delphi.
- or -
2.) PropType must not be thEnumeration. It should be either tkInteger or a new custom value like tkCEnumeration or whatever. Because thEnumeration means it has the enumeration TypeInfo with GetEnumName and GetEnumValue.

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny
Ondrej Pokorny wrote 24 lines
--- code end Run it in Delphi and FPC and compare the outputs: Delphi output: PropCount: 0 Property info is null FPC output: PropCount: 1 Property name: Fruit Property kind: tkEnumeration As you can see FPC creates a valid PropInfo for the property with valid TypeInfo (PI^.PropType) of the kind tkEnumeration! This is definitely wrong. So the solution is either: 1.) don't create PropInfo for the Fruit property like Delphi. - or - 2.) PropType must not be thEnumeration. It should be either tkInteger or a new custom value like tkCEnumeration or whatever. Because thEnumeration means it has the enumeration TypeInfo with GetEnumName and GetEnumValue.

tkInteger or no propinfo at all seems the only correct solution to me, with slight preference for no .

Reply by email

Ondrej Pokorny
to Michael Van Canneyt
Michael Van Canneyt wrote 30 lines
On Tue, 16 Aug 2022, Ondrej Pokorny via fpc-devel wrote: > --- code end > > Run it in Delphi and FPC and compare the outputs: > > Delphi output: > PropCount: 0 > Property info is null > > FPC output: > PropCount: 1 > Property name: Fruit > Property kind: tkEnumeration > > As you can see FPC creates a valid PropInfo for the property with > valid TypeInfo (PI^.PropType) of the kind tkEnumeration! This is > definitely wrong. > > So the solution is either: > > 1.) don't create PropInfo for the Fruit property like Delphi. > - or - > 2.) PropType must not be thEnumeration. It should be either tkInteger > or a new custom value like tkCEnumeration or whatever. Because > thEnumeration means it has the enumeration TypeInfo with GetEnumName > and GetEnumValue. tkInteger or no propinfo at all seems the only correct solution to me, with slight preference for no I forgot - a third solution that you and Martin suggested before

is also OK:
3.) compile error when the Fruit property is published (it is allowed only in the private..public sections).

So, I suggest:

*) Delphi mode:  ignore the PropInfo (Delphi compatibility) *) ObjFpc mode: compiler error (because you prefer no propinfo at all and ObjFpc is usually more strict than Delphi).

I've done some PropInfo work before, I assume I could prepare a patch for it. Can I start with this work?

Ondrej

Reply by email

Michael Van Canneyt
to Ondrej Pokorny
Ondrej Pokorny wrote 44 lines
Am 16.08.2022 um 13:19 schrieb Michael Van Canneyt via fpc-devel: > On Tue, 16 Aug 2022, Ondrej Pokorny via fpc-devel wrote: >> --- code end >> >> Run it in Delphi and FPC and compare the outputs: >> >> Delphi output: >> PropCount: 0 >> Property info is null >> >> FPC output: >> PropCount: 1 >> Property name: Fruit >> Property kind: tkEnumeration >> >> As you can see FPC creates a valid PropInfo for the property with >> valid TypeInfo (PI^.PropType) of the kind tkEnumeration! This is >> definitely wrong. >> >> So the solution is either: >> >> 1.) don't create PropInfo for the Fruit property like Delphi. >> - or - >> 2.) PropType must not be thEnumeration. It should be either tkInteger >> or a new custom value like tkCEnumeration or whatever. Because >> thEnumeration means it has the enumeration TypeInfo with GetEnumName >> and GetEnumValue. > > tkInteger or no propinfo at all seems the only correct solution to me, > with > slight preference for no propinfo at all. Sorry I forgot - a third solution that you and Martin suggested before is also OK: 3.) compile error when the Fruit property is published (it is allowed only in the private..public sections). So, I suggest: *) Delphi mode:  ignore the PropInfo (Delphi compatibility) *) ObjFpc mode: compiler error (because you prefer no propinfo at all and ObjFpc is usually more strict than Delphi). I've done some PropInfo work before, I assume I could prepare a patch for it. Can I start with this work?

Sure. You definitely have my blessing :-)

Michael.

Reply by email