[fpc-devel] potential bug, generics vs helpers

Sven Barth pascaldragon at googlemail.com
Fri Jan 27 17:47:53 CET 2012


Am 27.01.2012 17:11, schrieb Kornel Kisielewicz:
> On Fri, Jan 27, 2012 at 4:54 PM, Sven Barth<pascaldragon at googlemail.com>  wrote:
>> Am 27.01.2012 16:23, schrieb Kornel Kisielewicz:
>>> we define TA and TB in a different units it doesn't work anymore --
>>> which isn't surprising. So either the manual should be updated, or the
>>> "reusage" of the generic should be removed.
>>
>> In my opinion the documentation should be updated, because in Delphi (and
>> FPC's mode Delphi in trunk) you use code like the following:
>>
>> procedure Foo;
>> var
>>   A: TList<Pointer>;
>>   B: TList<Pointer>;
>> begin
>>   A := TList<Pointer>;
>>   B := A;
>> end;
>
> Does it also work (in Delphi and in FPC Delphi mode) if A and B would
> be defined in different units?

I'll need to try that in Delphi (which is the reference implementation 
after all for this).

>
> If I understand generics correctly, this is not so simple -- in which
> object file does TList<Pointer>  if it is used in two different units?
> It shouldn't go to the object file of the unit with TList, because it
> might not be used in a different compilation. It should also not go
> into either unit because that would make them depend on each other...
>

I'll go into details a bit for your question below.

>>> ... then TA and TB are treated as separate types, hence each has it's
>>> own helper -- program outputs "AB".
>>>
>>
>> This is interesting. In my opinion (under the aspect that TA = TB currently)
>> it should print "BB" in both cases. I'll need to investigate this.
>>
>> Also the case that both spezializations in the same unit are treated the
>> same, but aren't in the other one. Also something I'll need to investigate
>> (also in FPC 2.7.1).
>
> Well but this would introduce the need to either duplicate the
> generated code for each unit, or keeping in the TList unit object, or
> do I understand the compilation process incorrectly?
>

First there is the following differentiation in the compiler: you have 
symbols and definitions. For example

type
   TFoo = class

   end;

creates a definition TFoo to which a symbol TFoo points to. If you now do a

type
   TBar = TFoo

then you'll have a symbol TBar which also points to the definition TFoo.

For generics you'll need to know this: while parsing them a definition 
and a symbol (more correctly: often two) are created. E.g.

type
   generic TGeneric<T> = class
   end;

will generate a definition TGeneric$1 and the symbols TGeneric (which 
will point to TGeneric$1 in non-Delphi modes and to a special 
"unassigned definition" in mode Delphi) and TGeneric$1 which will point 
to TGeneric$1 (the logic behind this is related to the fact that Delphi 
allows you to overload generics; thus you can easily define TGeneric<T> 
and TGeneric<T, S> and also a non generic TGeneric in one single unit - 
this only works in mode Delphi in FPC's trunk). But no code will be 
generated for the generic only the "tokens" will be saved into the 
compiled unit (the PPU).

If you now specialize a generic the saved tokenstream will be loaded and 
passed to the parser which simply "reparses" the tokenstream with the 
only difference that the definition will now be e.g. for 
TGeneric<LongInt> TGeneric$1$LongInt and the symbol will be named 
whatever you called the type name.

E.g.

type
   TLongIntGeneric = specialize TGeneric<LongInt>;

Here the symbol will be named TLongIntGeneric and point to 
TGeneric$1$LongInt.

Another example (mode Delphi):

var
   longintgeneric: TGeneric<LongInt>;

Here no typesymbol will be created, but a variable symbol called 
longintgeneric of which the definiton will point to TGeneric$1$LongInt.

Now the point is that the compiler tries to optimize the code generation 
a bit and thus tries to generate the definition as few times as 
possible. If you thus put both examples in the same unit e.g. in mode 
Delphi like the following:

type
   TLongIntGeneric = TGeneric<LongInt>;
var
   longintgeneric: TGeneric<LongInt>;

then the definiton TGeneric$1$LongInt will only be generated once 
(otherwise you'd get symbol duplication errors, because of members of 
TGeneric).

The specialization process does not look in other units though. So if 
you have already specialized a generic in another unit with a certain 
type than it will (currently) be different from the one in the current 
unit (if it is specialized with the same type).

So currently the situation is like follows:
* specializations of generics with a type are considered the same if 
they happen in the same unit
* specializations of generics with a type are considered different if 
they happen in different units.
Note: If Delphi does not handle point 2 the same way then it's very 
likely that this will be changed in the future

>>> 2) In the second program, is it OK that the compiler silently discards
>>> the first Helper class, or maybe should it produce an error? Note that
>>> this also works for non-generic records (what does Delphi do?)
>>
>> As I said above: given that TA is considered equal to TB it should print the
>> same letter in both cases.
>
> So multiple helpers for a single class are allowed? And the order in
> which they appear defines which overloaded names are used?

You can write multiple helpers for a class, but only one helper will be 
completely active which is defined by the last helper that has been 
added to the scope (though I still need to check whether it was correct 
that in your example "A" was printed).

E.g.:

unit A defines:
MyHelperA = class helper for TObject
   procedure Foo;
   procedure Bar;
end;

unit B defines:
MyHelperB = class helper for TObject
   procedure Foo;
   procedure Blubb;
end;

main program:

uses
   MyHelperA, MyHelperB;

var
   o: TObject;
begin
   o.Foo; // => MyHelperB.Foo
   o.Bar; // => Error, identifer not found
   o.Blubb; // => MyHelperB.Blubb
end.

main program 2:

uses
   MyHelperB, MyHelperA;

var
   o: TObject;
begin
   o.Foo; // => MyHelperA.Foo
   o.Bar; // => MyHelperA.Bar
   o.Blubb; // => Error, identifer not found
end.

>>> 3) (most important for me) this all was discovered because I wanted to
>>> find a way to have two different "flavors" of a base record, extended
>>> by different helpers (eg. Vector4D base class extended to a
>>> Quaternion, and a Color) -- using generics provided a easy way to make
>>> a base record "evolve" into two distinct types, so in one unit I have
>>> TGLColor4 = specialize TVector4<    Single>; with a TGGColor4Helper and
>>> in another I have TQuaternion = specialize TVector4<Single>    with a
>>> TQuaternionHelper). I really like this solution, but depending on the
>>> way the above may change, I don't know if it won't be "fixed" not to
>>> work anymore in a later version of FPC?
>>
>>
>> Currently they will sometimes be the same and sometimes not. This would be
>> ok if we wouldn't have helpers, but as we have helpers now as well a
>> solution for this needs to be found.
>
> In this case, would there be another possibility to differentiate base
> objects for helpers? As in manually force the compiler to treat types
> differently? This would be extremely useful in a case like mine. I
> tried "type TBRecord = type TARecord;" but that also didn't work.

I don't know a solution for this currently... sorry.

As a sidenote to my lengthy explanation above: helpers are 
differentiated based on definitions not symbols (which is Delphi compatible)

Regards,
Sven



More information about the fpc-devel mailing list