* * *

Author Topic: Strange compiler behavior with generics [BUG?]  (Read 1379 times)

soerensen3

  • New member
  • *
  • Posts: 13
Strange compiler behavior with generics [BUG?]
« on: June 22, 2017, 01:34:38 am »
Hello,

i have encountered a strange behavior with the compiler when using generics.
I want to have a generic where define the parent class as a parameter. This way I can fake something like multiple inheritance.

When I compile this code I get this error

Code: Pascal  [Select]
  1.   generic gTest < Test > = class ( Test ) // <-- Gives me this error: class type expected, but got "<undefined type>"
  2.   end;        
  3.  

This code again compiles fine if I make a constraint to the generic parameter.
Code: Pascal  [Select]
  1.   generic gTest < Test: TPersistent > = class ( Test )
  2.     function Test123: String; // Some function. This even works with properties and constructors and so on
  3.   end;      
  4.  

Everything works as expected until I try the following:
Code: Pascal  [Select]
  1.   generic gTest < Test: TPersistent > = class ( Test )
  2.     destructor Destroy; override; // <- Gives me: There is no method in an ancestor class to be overridden: "destructor Destroy;"
  3.     function Test123: String;
  4.   end;  
  5.  

It says TPersistent does not have a virtual destructor which is simply not true.
Code: Pascal  [Select]
  1.   TPersistent = class(TObject,IFPObserved)  
  2.      ...
  3.     Destructor Destroy; override;
  4.  

Should I file a bug report?

See attachment for the full code.
« Last Edit: June 22, 2017, 03:45:02 pm by soerensen3 »
Lazarus 1.6.4 with FPC 3.0.2
Target: Fedora Linux 64 Bit (korora 4.5.6-200.fc23.x86_64)

molly

  • Hero Member
  • *****
  • Posts: 1695
Re: Strange compiler behavior with generics
« Reply #1 on: June 22, 2017, 02:52:48 am »
If you wish to inherit from TPersistent then you would have to tell the compiler that you wish so

I think you wanted something like this:
Code: Pascal  [Select]
  1. program project1;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. Uses
  6.   Classes;
  7.  
  8. type
  9.   { gTest }
  10.   generic gTest<T> = class(TPersistent)
  11.     destructor Destroy; override;
  12.     function Test123: String;
  13.   end;
  14.  
  15.   TTest = specialize gTest<TPersistent>;
  16.  
  17. destructor gTest.Destroy;
  18. begin
  19.  
  20. end;
  21.  
  22. function gTest.Test123: String;
  23. begin
  24.   Result:= 'TestString';
  25. end;
  26.  
  27. var
  28.   Test: TTest;
  29. begin
  30.   Test:= TTest.Create;
  31.   WriteLn( Test.Test123 );
  32.   Test.Free;
  33. end.
  34.  
Note the warning btw.

Also note that the specialization makes no sense to me, unless it was intended.

Perhaps it would be helpful to read up on generics wiki ?
« Last Edit: June 22, 2017, 03:02:46 am by molly »

Thaddy

  • Hero Member
  • *****
  • Posts: 4034
Re: Strange compiler behavior with generics
« Reply #2 on: June 22, 2017, 09:08:39 am »
Well I could come up with this,maybe you mean something along those lines:
Code: Pascal  [Select]
  1. program Project1;
  2. {$mode delphi}
  3. uses classes;
  4.  
  5. type
  6.   TPersistentclass = class of Tpersistent;
  7.   TMulti<T1,T2:TPersistent> = class(TPersistent)
  8.   strict private
  9.   var a:T1;
  10.   var b:T2;
  11.   public
  12.     constructor create;
  13.     destructor destroy;override;
  14.     property class1:T1 read a;
  15.     property class2:T2 read b;
  16.   end;
  17.  
  18.   constructor TMulti<T1,T2:Tpersistent>.Create;
  19.   begin
  20.      a:=T1.Classtype.Create as T1;  // note as is important here.
  21.      b:=T2.ClassType.Create as T2;
  22.   end;
  23.  
  24.  destructor TMulti<T1,T2:Tpersistent>.Destroy;
  25.  begin
  26.     b.free;
  27.     a.free;
  28.     inherited;
  29.  end;
  30.  
  31. var
  32.   a:TMulti<Tcomponent,TStringlist>;  // a new class type from Tcomponent and TStringlist
  33. begin
  34.   a := TMulti<Tcomponent,Tstringlist>.Create;
  35.   writeln(a.ClassName);  // The new class
  36.   writeln(a.Class1.ClassName); // the component
  37.   writeln(a.Class2.ClassName); // the stringlist
  38.   a.free;
  39.   readln;
  40. end.    
  41.  

This works in trunk... It is a pity that code completion doesn't work with generics yet... Then this would be more useful. O:-) 8-)
« Last Edit: June 22, 2017, 09:16:53 am by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

soerensen3

  • New member
  • *
  • Posts: 13
Re: Strange compiler behavior with generics
« Reply #3 on: June 22, 2017, 12:50:15 pm »
Thanks for the answers and sorry for the confusion!
@Molly: I did not mean to inherit TPersistent directliy. I will try to make a better sample.
Code: Pascal  [Select]
  1.    generic gNamedObject < CParent > = class ( CParent )
  2.       private
  3.          FName: String;
  4.       published
  5.         property Name: String read FName write FName;
  6.    end;
  7.  
  8.   TPersistentWithName = specialize gNamedObject < TPersistent >;
  9.   TStringListWithName = specialize gNamedObject < TStringList >;
  10.  
  11.   generic gObjectWithChildren <CParent> = class ( CParent )
  12.     private
  13.        type TCustomList = specialize TFPGObjectList<CParent>;
  14.        var FChildren: TCustomList; //Should be initialized in constructor and destroyed in destructor
  15.  
  16.     public
  17.        property Children: TCustomList read FChildren write FChildren;
  18.   end;
  19.  
  20.   TNamedPersistentWithChildren = specialize gNamedObject < specialize gObjectWithChildren < TPersistent >>;
  21.  
So I have something like multiple inheritance because I can inherit from a generic with any object at any time to give it some behavior if needed. The above example works if I make a constraint to the generic parameter to be a descendant of TPersistent. I could have used TObject as well.
This is not a big deal but in the lower generic I would need to introduce a constructor and destructor to initalize FChildren. This however leads to the message:
Quote
There is no method in an ancestor class to be overridden: "destructor Destroy;"

While I think Thaddy got what I meant by multiple inheritance my actual question is, if this is a compiler bug, because there is a virtual destructor declared in TObject so any class should be able to override the Destroy method.
Lazarus 1.6.4 with FPC 3.0.2
Target: Fedora Linux 64 Bit (korora 4.5.6-200.fc23.x86_64)

Thaddy

  • Hero Member
  • *****
  • Posts: 4034
Re: Strange compiler behavior with generics
« Reply #4 on: June 22, 2017, 02:31:25 pm »
While I think Thaddy got what I meant by multiple inheritance my actual question is, if this is a compiler bug, because there is a virtual destructor declared in TObject so any class should be able to override the Destroy method.
But that is exactly what I did.. O:-) Override the destructor... 8-)
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

soerensen3

  • New member
  • *
  • Posts: 13
Re: Strange compiler behavior with generics
« Reply #5 on: June 22, 2017, 03:39:54 pm »
@Thaddy:
yes I didn't mean than in general you can not override the constructor but in my special case you can't. I think this is a bug in the compiler because every other virtual method can be overridden.

So to summarize:
  • generic without parameter used as a class descendant: Works even with overriding the destructor
  • generic with parameter used as a class descendant: Works except for overriding the destructor


Also interesting that the error occurs even without the specialize declaration.
Lazarus 1.6.4 with FPC 3.0.2
Target: Fedora Linux 64 Bit (korora 4.5.6-200.fc23.x86_64)

rvk

  • Hero Member
  • *****
  • Posts: 2566
Re: Strange compiler behavior with generics [BUG?]
« Reply #6 on: June 22, 2017, 04:11:10 pm »
Could you create a complete reproducible example?

You already said you didn't mean to inherit from TPersistent but five an example CParent. How does CParent look. If that again is inherited from TObject it should have the destructor to be overridden.

This compiles:
Code: Pascal  [Select]
  1. type
  2.   CParent = class(TPersistent)
  3.   end;
  4.  
  5.   generic gTest<Test: CParent> = class(CParent)
  6.   public
  7.     destructor Destroy; override;
  8.     function Test123: string;
  9.   end;

So please show your "special case" where you can't.

soerensen3

  • New member
  • *
  • Posts: 13
Re: Strange compiler behavior with generics [BUG?]
« Reply #7 on: June 22, 2017, 04:34:03 pm »
CParent can be any class type as any class inherits TObject.

Ok try to compile the following program once as is and second with the define in the beginning enabled.
Code: Pascal  [Select]
  1.    
  2. program GenericTest;
  3.  
  4. {.$DEFINE WITHDESTRUCTOR}
  5. //REMOVE DOT IN THE BEGINNING TO ENABLE
  6. //WORKS WITH DEFINE DISABLED, DOES NOT WORK WITH DEFINE ENABLED
  7.  
  8. {$mode objfpc}{$H+}
  9.  
  10. uses
  11.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  12.   cthreads,
  13.   {$ENDIF}{$ENDIF}
  14.   Classes,
  15.   fgl
  16.   { you can add units after this };
  17.  
  18. type
  19. generic gNamedObject < CParent: TObject > = class ( CParent )
  20.       private
  21.          FName: String;
  22.       published
  23.         property Name: String read FName write FName;
  24.    end;
  25.  
  26.   TPersistentWithName = specialize gNamedObject < TPersistent >;
  27.   TStringListWithName = specialize gNamedObject < TStringList >;
  28.  
  29.   generic gObjectWithChildren < CParent: TObject > = class ( CParent )
  30.     private
  31.        type TCustomList = specialize TFPGObjectList<CParent>;
  32.        var FChildren: TCustomList; //Should be initialized in constructor and destroyed in destructor
  33.  
  34.     public
  35.        constructor Create;
  36.        {$IFDEF WITHDESTRUCTOR}
  37.        destructor Destroy; override; //Here the error is raised that this method is not in the base class
  38.       {$ENDIF}
  39.  
  40.        property Children: TCustomList read FChildren write FChildren;
  41.   end;
  42.  
  43.   TPersistentWithChildren = specialize gObjectWithChildren < TPersistent >;
  44.   TNamedPersistentWithChildren = specialize gNamedObject < TPersistentWithChildren >;
  45.  
  46. constructor gObjectWithChildren.Create;
  47. begin
  48. end;
  49.  
  50. {$IFDEF WITHDESTRUCTOR}
  51. destructor gObjectWithChildren.Destroy;
  52. begin
  53. end;
  54. {$ENDIF}
  55.  
  56. begin
  57. end.
  58.  
Lazarus 1.6.4 with FPC 3.0.2
Target: Fedora Linux 64 Bit (korora 4.5.6-200.fc23.x86_64)

rvk

  • Hero Member
  • *****
  • Posts: 2566
Re: Strange compiler behavior with generics [BUG?]
« Reply #8 on: June 22, 2017, 04:38:55 pm »
Maybe I'm completely off but why not
Code: Pascal  [Select]
  1.   generic gObjectWithChildren < CParent: TObject > = class ( TObject )
Inherit from TObject in class().

CParent is not an object you can use in class(), is it?

« Last Edit: June 22, 2017, 04:46:21 pm by rvk »

Thaddy

  • Hero Member
  • *****
  • Posts: 4034
Re: Strange compiler behavior with generics [BUG?]
« Reply #9 on: June 22, 2017, 04:51:11 pm »
CParent can be any class type as any class inherits TObject.
You asked for TPersistent yourself!.
Just replace it with "Tobject" (or better with "class") and my example still works as expected.
Code: Pascal  [Select]
  1. program Project1;
  2. {$mode delphiunicode}
  3. uses classes;
  4.  
  5. type
  6.   TMulti<T1,T2:class> = class
  7.   strict private
  8.   var a:T1;
  9.   var b:T2;
  10.   public
  11.     constructor create;
  12.     destructor destroy;override;
  13.   public
  14.     property class1:T1 read a;
  15.     property class2:T2 read b;
  16.   end;
  17.  
  18.   constructor TMulti<T1,T2:class>.Create;
  19.   begin
  20.      a:=T1.Classtype.Create as T1;
  21.      b:=T2.ClassType.Create as T2;
  22.   end;
  23.  
  24.  destructor TMulti<T1,T2:class>.Destroy;
  25.  begin
  26.     b.free;
  27.     a.free;
  28.     inherited;
  29.  end;
  30.  
  31.  
  32. var
  33.   a:TMulti<Tcomponent,TStringlist>;
  34. begin
  35.   a := TMulti<Tcomponent,Tstringlist>.Create;
  36.   writeln(a.ClassName);
  37.   writeln(a.Class1.ClassName);
  38.   writeln(a.Class2.ClassName);
  39.   a.free;
  40.   readln;
  41. end.

Note I strongly suggest to use Delphi syntax for generics and not quirks mode.
« Last Edit: June 22, 2017, 04:58:11 pm by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

ASerge

  • Sr. Member
  • ****
  • Posts: 340
Re: Strange compiler behavior with generics [BUG?]
« Reply #10 on: June 22, 2017, 05:03:00 pm »
The author tries to make a strange definition - to specify the template symbol as the base class.
I think this is a compiler error. It must explicitly state error that the base type must be a class (as Delphi does in a similar construct).

soerensen3

  • New member
  • *
  • Posts: 13
Re: Strange compiler behavior with generics [BUG?]
« Reply #11 on: June 22, 2017, 05:09:02 pm »
@rvk:

Quote
Maybe I'm completely off but why not Inherit from TObject in class().
Because it is not what I want. I want a generic that can be applied to any class to add new behavior. Therefore a Parent parameter is needed in the generic.

Quote
CParent is not an object you can use in class(), is it?
It's a variable you declared.

CParent is the generic parameter: http://wiki.freepascal.org/Generics
It is defined in the <..> brackets and serves as a placeholder for the generic parameter until specialized.

@Thaddy:
While your code works you will end up with an TObject descendand but with 2 properties instead of real inheritance, which is working as expected except for the destructor to be overridable.


Anyway it is not the point how I can do it some other way but if it is a bug in the compiler or not that the code supplied does not compile. I believe yes but I wanted a second opinion. Hope this didn't sound rude, I only wanted to clearify! :)
Lazarus 1.6.4 with FPC 3.0.2
Target: Fedora Linux 64 Bit (korora 4.5.6-200.fc23.x86_64)

ASerge

  • Sr. Member
  • ****
  • Posts: 340
Re: Strange compiler behavior with generics [BUG?]
« Reply #12 on: June 22, 2017, 05:20:19 pm »
@rvk:
Quote
Maybe I'm completely off but why not Inherit from TObject in class().
Because it is not what I want. I want a generic that can be applied to any class to add new behavior. Therefore a Parent parameter is needed in the generic.
I suspect you do not really understand generics. Perhaps the class helper is more suitable for you?
Code: Pascal  [Select]
  1. {$APPTYPE CONSOLE}
  2. program Project1;
  3.  
  4. uses Classes;
  5.  
  6. type
  7.   TAnyClassHelper = class helper for TObject
  8.     function Test123: String;
  9.   end;
  10.  
  11. function TAnyClassHelper.Test123: string;
  12. begin
  13.   Result := '123';
  14. end;
  15.  
  16. var
  17.   X: TStream;
  18. begin
  19.   X := TMemoryStream.Create;
  20.   Writeln(X.Test123);
  21.   X.Free;
  22.   Readln;
  23. end.

soerensen3

  • New member
  • *
  • Posts: 13
Re: Strange compiler behavior with generics [BUG?]
« Reply #13 on: June 22, 2017, 06:02:05 pm »
I filed a bug report: https://bugs.freepascal.org/view.php?id=32054

I have the feeling that nobody really understands what I'm saying ;)

A class helper can not have fields so this is not possible for a class helper.
Code: Pascal  [Select]
  1. generic gNamedObject < CParent: TObject > = class ( CParent )
  2.       private
  3.          FName: String;
  4.       published
  5.         property Name: String read FName write FName;
  6.    end;

This code works with and can add the functionality of having a name to any class by specializing.
Code: Pascal  [Select]
  1. TPersistentWithName = specialize gNamedObject < TPersistent >;
  2.   TStringListWithName = specialize gNamedObject < TStringList >;

I can use many of these generics (and store eg. Positions, Lists with Children and so on) and specialize them in a chain so i get an object with multiple behaviors I define only once. So again the code above is valid and working.

As any object has a destructor Destroy; virtual; it should be possible to override it in a generic. But it's not. For no reason I think.


My only question is: Is this behavior a bug? If not what is the reason that you cannot override the destructor.
Lazarus 1.6.4 with FPC 3.0.2
Target: Fedora Linux 64 Bit (korora 4.5.6-200.fc23.x86_64)

Thaddy

  • Hero Member
  • *****
  • Posts: 4034
Re: Strange compiler behavior with generics [BUG?]
« Reply #14 on: June 22, 2017, 08:45:00 pm »
No, it is a blatant programming error. You can't derive from an instance of a class in your case CParent, you must derive from the class, in your case TObject.
That makes it clear. This is NOT a bug. Sorry. This is your mistake. That can only be done in scripting/dynamic languages and only at runtime, not compile time and I never saw this implemented like you propose.
But your general idea in your original code is possible, as I showed.

Also note it is possible to override a destructor in a generic class: again, I showed you how... I think you do not understand my code.
And I refuse to rewrite it in quirks mode (objfpc style generics mode). O:-)
« Last Edit: June 22, 2017, 08:55:54 pm by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus