XE6 to XE7 Update 1 Generics incompatibility.

XE6 to XE7 Update 1 Generics incompatibility.
I can't say for sure if I ever tried this in XE7 first version, but I don't think I did.

procedure TAbstractBaseFactory.CreateInner;
begin
  FInner := TBaseTypeClass(T).Create;  
end;

TBaseTypeClass(T).Create;  compiles and runs in XE6, but gives
[dcc32 Error] : E2010 Incompatible types: 'T' and 'TAbstractBaseType'
in XE7.

Stefan Glienke - You showed me this trick for XE6 - any suggestions for XE7?

{code}

program CreateFromGenericTClassXE7;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TAbstractBaseType = class
  public
    Constructor Create; virtual;
    procedure DoSomething; virtual; abstract;
  end;

  TDescendantType = class(TAbstractBaseType)
  public
    procedure DoSomething; override;
  end;

type
  TBaseTypeClass = class of TAbstractBaseType;

type
  TAbstractBaseFactory = class abstract (TObject)
  private
    FInner: T;
    procedure SetInner(const Value: T);
  public
    constructor Create;
    procedure CreateInner; virtual;
    property Inner: T read FInner write SetInner;
  end;


{ TAbstractBaseFactory }

constructor TAbstractBaseFactory.Create;
begin
  Inherited;
end;

procedure TAbstractBaseFactory.CreateInner;
begin
  FInner := TBaseTypeClass(T).Create;  // [dcc32 Error] : E2010 Incompatible types: 'T' and 'TAbstractBaseType'
end;

procedure TAbstractBaseFactory.SetInner(const Value: T);
begin
  FInner := Value;
end;

{ TDescendantType }

procedure TDescendantType.DoSomething;
begin

end;

{ TAbstractBaseType }

constructor TAbstractBaseType.Create;
begin
  Inherited;
end;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Comments

  1. Edit (what I wrote earlier was crap - I should have thought a bit more before writing): 

    This is a fix for a compiler error you got earlier up to XE6 when you instantiated the type.

    Add this line to your main:
    TAbstractBaseFactory.Create.CreateInner;

    It will raise a E2010 Incompatible types: 'TDescendantType' and 'TAbstractBaseType' in some completely off location up to XE6. That is because you are assigning the result of the TAbstractBaseType ctor which is always a TAbstractBaseType to T which of course is of TAbstractBaseType but due to the generic inherits from it.

    So in both cases (before XE7 and after XE7) you have to cast FInner to TAbstractBaseType to make this work.

    ReplyDelete
  2. Stefan Glienke - I agree, but embrace your workaround ;)

    ReplyDelete
  3. Lars Fosdal I edited what I wrote earlier. So it's not a bug but a fix for some very annoying bug actually. I cannot say how many times I had to incrementally comment out code to get to the line that really caused the error because the compiler stopped at some completely off locations.

    ReplyDelete
  4. Interestingly, this did not solve it!

    [dcc32 Error] PSD_db_FireDAC.pas(1533): E2010 Incompatible types: 'TFDPhysDriverLink' and 'FireDAC.Phys.TFDPhysDriverLink'

    ReplyDelete
  5. Also - in another context - there is no XE7.1 in QP

    ReplyDelete
  6. Lars Fosdal Then your code is wrong elsewhere. Also just posting the error does not help...

    ReplyDelete
  7. Parametric wise, the code was identical to the example.

    type
      TFDPhysDriverLinkClass = class of TFDPhysDriverLink;
      TPSDFireDatabasePool = class abstract (TPSDAbstractDatabasePool)
      private
        FFireDriverLink: T;
        procedure SetFireDriverLink(const Value: T);
      protected
        function CreateResourceInstance: TPSDDatabase_Abstract; override;
      public
        class function UseThreadCache: Boolean; override;
        function DriverIdentity: String; override;
        procedure DriverInit; override;
        procedure DriverDone; override;
        property FireDriverLink: T read FFireDriverLink write SetFireDriverLink;
      end;

    ...
    Originally:
    procedure TPSDFireDatabasePool.DriverInit;
    begin
      FireDriverLink := TFDPhysDriverLinkClass(T).Create(nil);
    end;

    If I change it to 
    procedure TPSDFireDatabasePool.DriverInit;
    begin
      TFDPhysDriverLink(FFireDriverLink) := TFDPhysDriverLinkClass(T).Create(nil);
    end;

    I get the above error.

    ReplyDelete
  8. I have a workaround - implement DriverInit in each of the database specific pools, but it just seems so unnecessary.

    ReplyDelete
  9. First my belief is that the old code never actually worked when using the class with T being something else than TFDPhysDriverLink exactly (the reason I mentioned above).

    The error leads me to the guess that you have some naming conflict. Do you have some other type called TFDPhysDriverLink or called the generic type parameter like that?

    ReplyDelete
  10. The actual pool used in declared in a different unit.

    unit PSD_db_FireDAC_MSSQL;

    interface
    uses
      PSD_db_FireDAC, FireDAC.Stan.Consts, FireDAC.Phys.ODBCBase, FireDAC.Phys.MSSQL;

    type
      TPSDFireDatabasePoolMSSQL = class(TPSDFireDatabasePool);

    implementation

    end.

    This is done to minimize pulling in code specific to the database.

    ReplyDelete
  11. Nope, TFDPhysDriverLink is from FireDAC.

    Changing to 
      TPSDFireDatabasePool = class abstract (TPSDAbstractDatabasePool)
      public
        type   TFDPhysDriverLinkClass = class of FireDAC.Phys.TFDPhysDriverLink;

    doesn't help. Same error.

    ReplyDelete
  12. Well unless the compiler went completely bonkers (did you restart the IDE?) the error: Incompatible types: 'TFDPhysDriverLink' and 'FireDAC.Phys.TFDPhysDriverLink' is pretty clear. Ctrl+Click on the type used to hardcast FFireDriverLink and check where that leads you.

    ReplyDelete
  13. Well, after a restart and cleaning out units, it now mysteriously works.

    ReplyDelete
  14. Doh - I found what I'd change to break to the code - I rolled back the code to redo the changes before my last comment- and now I spotted the diff.

    I had moved the declaration of the 
    type
      TFDPhysDriverLinkClass = class of FireDAC.Phys.TFDPhysDriverLink;

    inside the abstract class. (See comment from 2:28)

    That did not help :P

    It's a bit strange, though - that declaration is pretty specific.

    ReplyDelete
  15. Lars Fosdal So you took careful aim and shot your toes off? :)

    ReplyDelete
  16. Lars Fosdal Following code also works:
      TAbstractBaseFactory = class abstract (TObject)

    and

    procedure TAbstractBaseFactory.CreateInner;
    begin
      FInner := T.Create; 
    end;

    ReplyDelete
  17. Dalija Prasnikar No, it does not because the ctor constraint does only work with the default ctor. Maybe in the example code it will work but the real code is dealing with TComponent classes.

    ReplyDelete
  18. Stefan Glienke You are correct, my code only works with parameterless ctor and I missed that real class in use is based on TComponent.

    ReplyDelete
  19. Surely this should be fixable, as there would be only one virtual or nonvirtual ctor in the abstract class parameter specifier that matches the call.

    ReplyDelete
  20. Bill Meyer​ This missed the target. Shooting your foot off requires running code ;)

    ReplyDelete
  21. Lars Fosdal Well. So all you did then was maim yourself?

    ReplyDelete
  22. Can you maim yourself with a gun that won't load ammo? Bar cutting myself on a bayonet...

    ReplyDelete
  23. Lars Fosdal See you're getting all fussy over technicalities. Broken is broken. The flavor makes no difference. :-)

    ReplyDelete
  24. Bill Meyer bah, it's just a flesh wound!

    ReplyDelete
  25. I guess you can say that while trying to load the ammo, I bruised a finger when trying to insert the cartridge the wrong way. I did however identify the problem, and let the world know that was my mistake.  I still needed to typecast the magazine to insert it, though - something that previously was not necessary.  So much metaphors! ;)

    ReplyDelete
  26. "something that previously was not necessary"

    I still don't believe that because the E2010 will occur when you instantiate the generic class - at least it did on XE and XE6 with the example code in the original post.

    ReplyDelete
  27. I have the FireDAC instantiation code correctly running in XE6.1 without the typecast.  In XE7.x that code won't compile without the typecast.

    ReplyDelete
  28. Lars Fosdal There were some changes in XE7 compiler that might be connected with this. I have private report that was closed "As designed" because there were some fixes that introduced regression. While my report is slightly different, root cause might be the same.

    [XE7 REGRESSION] Bogus "Incompatible types" error
    http://qc.embarcadero.com/wc/qcmain.aspx?d=126567

    This is simplified test case from that report for those that are not able to see it:

    type Constraint = class end;

    type G = class // or RECORD, or INTERFACE

    procedure Method(const X: T);

    property Setter: T write Method;
    end;

    procedure G.Method(const X: T);
    begin
    end;

    type Test = class

    F: G;

    procedure E2010(const X: Constraint);
    end;

    procedure Test.E2010(const X: Constraint);
    begin

    F.Method(X); // OK

    F.Setter := X // E2010 Incompatible types 'T' and 'Constraint'
    end;

    var Instantiate: Test;

    end.

    ReplyDelete

Post a Comment