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.
I can't say for sure if I ever tried this in XE7 first version, but I don't think I did.
procedure TAbstractBaseFactory
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
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
begin
Inherited;
end;
procedure TAbstractBaseFactory
begin
FInner := TBaseTypeClass(T).Create; // [dcc32 Error] : E2010 Incompatible types: 'T' and 'TAbstractBaseType'
end;
procedure TAbstractBaseFactory
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.
Edit (what I wrote earlier was crap - I should have thought a bit more before writing):
ReplyDeleteThis 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.
Stefan Glienke - I agree, but embrace your workaround ;)
ReplyDeleteLars 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.
ReplyDeleteInterestingly, this did not solve it!
ReplyDelete[dcc32 Error] PSD_db_FireDAC.pas(1533): E2010 Incompatible types: 'TFDPhysDriverLink' and 'FireDAC.Phys.TFDPhysDriverLink'
Also - in another context - there is no XE7.1 in QP
ReplyDeleteLars Fosdal Then your code is wrong elsewhere. Also just posting the error does not help...
ReplyDeleteParametric wise, the code was identical to the example.
ReplyDeletetype
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.
I have a workaround - implement DriverInit in each of the database specific pools, but it just seems so unnecessary.
ReplyDeleteFirst 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).
ReplyDeleteThe 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?
The actual pool used in declared in a different unit.
ReplyDeleteunit 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.
Nope, TFDPhysDriverLink is from FireDAC.
ReplyDeleteChanging to
TPSDFireDatabasePool = class abstract (TPSDAbstractDatabasePool)
public
type TFDPhysDriverLinkClass = class of FireDAC.Phys.TFDPhysDriverLink;
doesn't help. Same error.
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.
ReplyDeleteWell, after a restart and cleaning out units, it now mysteriously works.
ReplyDeleteDoh - 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.
ReplyDeleteI 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.
Lars Fosdal So you took careful aim and shot your toes off? :)
ReplyDeleteLars Fosdal Following code also works:
ReplyDeleteTAbstractBaseFactory = class abstract (TObject)
and
procedure TAbstractBaseFactory.CreateInner;
begin
FInner := T.Create;
end;
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.
ReplyDeleteStefan Glienke You are correct, my code only works with parameterless ctor and I missed that real class in use is based on TComponent.
ReplyDeleteSurely this should be fixable, as there would be only one virtual or nonvirtual ctor in the abstract class parameter specifier that matches the call.
ReplyDeleteBill Meyer​ This missed the target. Shooting your foot off requires running code ;)
ReplyDeleteLars Fosdal Well. So all you did then was maim yourself?
ReplyDeleteCan you maim yourself with a gun that won't load ammo? Bar cutting myself on a bayonet...
ReplyDeleteLars Fosdal See you're getting all fussy over technicalities. Broken is broken. The flavor makes no difference. :-)
ReplyDeleteBill Meyer bah, it's just a flesh wound!
ReplyDeleteI 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"something that previously was not necessary"
ReplyDeleteI 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.
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.
ReplyDeleteLars 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.
ReplyDelete[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.