[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