Jump to content
luebbe

Generics compiler Error "Incompatible Types"

Recommended Posts

Hi Folks,

 

I'm banging my head against the wall trying to understand, why the compiler throws "E2008 Incompatible Types" errors at the marked lines. I must be missing something completely... :classic_unsure:

 


 

program Project4;

{$APPTYPE CONSOLE}

{$R *.res}


uses
  System.Generics.Collections,
  System.SysUtils;

type
  IMyIntf = interface
    ['{FCAFF2E8-5F8E-4473-8795-89BD41C89D57}']
  end;

  TMyList<IMyIntf> = class
    FItems: TList<IMyIntf>;
    procedure AddItem(AItem: IMyIntf);
    function GetItem: IMyIntf;
  end;

  TMyType = class
  end;

  TMyTypeList<TMyType> = class
    FItems: TList<TMyType>;
    procedure AddItem(AItem: TMyType);
    function GetItem: TMyType;
  end;

{ TMyList<IMyIntf> }

procedure TMyList<IMyIntf>.AddItem(AItem: IMyIntf);
var
  Value: IMyIntf;
begin
  FItems.Add(AItem); // E2008 Incompatible Types
  FItems.Add(Value); // Compiler is happy
end;

function TMyList<IMyIntf>.GetItem: IMyIntf;
begin
  Result := FItems[0]; // E2008 Incompatible Types
end;

{ TMyTypeList<TMyType> }

procedure TMyTypeList<TMyType>.AddItem(AItem: TMyType);
var
  Value: TMyType;
begin
  FItems.Add(AItem); // E2008 Incompatible Types
  FItems.Add(Value); // Compiler is happy
end;

function TMyTypeList<TMyType>.GetItem: TMyType;
begin
  Result := FItems[0]; // E2008 Incompatible Types
end;

begin
  try
    { TODO -oUser -cConsole Main : Code hier einfügen }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

 

  • Like 1

Share this post


Link to post
Posted (edited)

I think the compiler is getting confused as your generic parameters matches the names of existing types.

 

if you rename the standalone IMyIntf to something else, then the generic type can remain TMyList<IMyIntf>, and similarly for TMyType, the generic types should be ok.

 

alternatively rename the generic parameters, and even placing constraints may be helpful. e.g.

TMyList<TIntf: IMyInf> = class 

...

TMyTypeList<T:class> = class  

...

Edited by darnocian

Share this post


Link to post
program Project4Fixed;

{$APPTYPE CONSOLE}

{$R *.res}


uses
  System.Generics.Collections,
  System.SysUtils;

type
  IMyIntf = interface
    ['{FCAFF2E8-5F8E-4473-8795-89BD41C89D57}']
  end;

  TMyList<T:IMyIntf> = class
    FItems: TList<T>;
    procedure AddItem(AItem: T);
    function GetItem: T;
  end;

  TMyType = class
  end;

  TMyTypeList<T:TMyType> = class
    FItems: TList<T>;
    procedure AddItem(AItem: T);
    function GetItem: T;
  end;

{ TMyList<IMyIntf> }

procedure TMyList<T>.AddItem(AItem: T);
var
  Value: T;
begin
  FItems.Add(AItem); // E2008 Incompatible Types No more - Compiler happy
  FItems.Add(Value); // Compiler is happy
end;

function TMyList<T>.GetItem: T;
begin
  Result := FItems[0]; // E2008 Incompatible Types No more - Compiler happy
end;

{ TMyTypeList<T> }

procedure TMyTypeList<T>.AddItem(AItem: T);
var
  Value: T;
begin
  FItems.Add(AItem); // E2008 Incompatible Types No more - Compiler happy
  FItems.Add(Value); // Compiler is happy
end;

function TMyTypeList<T>.GetItem: T;
begin
  Result := FItems[0]; // E2008 Incompatible Types No more - Compiler happy
end;

begin
  try
    { TODO -oUser -cConsole Main : Code hier einfügen }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

 

  • Like 1
  • Thanks 1

Share this post


Link to post

Thanks Lars,

 

once again this is proof, that T(ea) makes you happy...

  • Haha 1

Share this post


Link to post

Added advice for generic lists:

If you ever need a polymorph generic element type TMyType<T> as well, base it on a skeleton TMyType which contain the verbs and common props you need.

 

  TMyType = class
  public
    constructor Create; virtual; abstract;
    procedure SomeOperation; virtual; abstract;
  end;

  TMyType<T> = class(TMyType);
  public
    constructor Create; override;
    procedure SomeOperation; override;
  end;
    
  TMyTypeList<T:TMyType, constructor> = class
    FItems: TList<T>;
    procedure AddItem(AItem: T);
    function GetItem: T;
  end;

 

Share this post


Link to post

I think the skeleton is what I'm heading towards.

 

Next question. How can I implement a callback with the list itself? I tried different variations, but this is as far as I can get.


 

type
  IMyIntf = interface;
  TMyList<T: IMyIntf> = class;

  TOnListItemSelected = procedure(AItem: IMyIntf) of object;
  TOnListUpdated = procedure(AList: TMyList<IMyIntf>) of object;

  IMyIntf = interface
    ['{FCAFF2E8-5F8E-4473-8795-89BD41C89D57}']
  end;

  TMyList<T: IMyIntf> = class
    FItems: TList<T>;
    FOnListUpdated: TOnListUpdated;
    procedure ListUpdated;
  end;
{ TMyList<T> }procedure TMyList<T>.ListUpdated;

begin
  if Assigned(FOnListUpdated) then
    FOnListUpdated(Self);  // E2010 Inkompatible Typen: 'Project4.TMyList<Project4.IMyIntf>' und 'Project4.TMyList<Project4.TMyList<T>.T>'
end;

 

Share this post


Link to post

The trick to generics is to only specify the desired type on the left side of a declaration and use <T> to "pass it on".

Something like this..

type
  IMyIntf = interface;
  TMyList<T: IMyIntf> = class;

  TOnListItemSelected<T:IMyIntf> = procedure(AItem: T) of object;
  TOnListUpdated<T:IMyIntf> = procedure(AList: TMyList<T>) of object;

  IMyIntf = interface
    ['{FCAFF2E8-5F8E-4473-8795-89BD41C89D57}']
  end;

  TMyList<T: IMyIntf> = class
    FItems: TList<T>;
    FOnListUpdated: TOnListUpdated<T>;
    procedure ListUpdated;
  end;

{ TMyList<T> }

procedure TMyList<T>.ListUpdated;
begin
  if Assigned(FOnListUpdated) then
    FOnListUpdated(Self); 
end;

 

  • Thanks 1

Share this post


Link to post

Arggh, same thing again. Thanks again. Hopefully this time it sticks in my brain.
It's not that I'm not working with generics a lot. It's just that I rarely build generics from scratch.

Share this post


Link to post

It takes a bit of practice - as all things.

Me - I struggle with interfaces since I rarely use them.

Share this post


Link to post

Meanwhile I prefer interfaces over abstract classes. It was the other way round 20 years ago.
Still struggling with the data passed in the callback, but I'll look into it again ten days from now. Finally a bit of vacation 🙂

Share this post


Link to post

Would methods exposed by IMyIntf be visible for references to T in the generic code without that constraint? 

Share this post


Link to post
37 minutes ago, Lars Fosdal said:

Would methods exposed by IMyIntf be visible for references to T in the generic code without that constraint? 

The generic code doesn't refer to any methods, not that the interface even has a hy methods here. 

Share this post


Link to post

It is true that if the generic code does not need to touch methods from the interface, the generic type constraint<T:sometype> can be eliminated, but who am I to say what @luebbe will put in his interfaces and code?.

Share this post


Link to post
34 minutes ago, Lars Fosdal said:

It is true that if the generic code does not need to touch methods from the interface, the generic type constraint<T:sometype> can be eliminated, but who am I to say what @luebbe will put in his interfaces and code?.

Sure. I just read your very first post and my initial reaction was that you'd changed two things and the OP may have got the wrong impression that both were needed to resolve the compilation error. 

Share this post


Link to post

Back from vacation...

Thanks for further feedback, I probably oversimplified the interface in order to produce a minimal example. In fact I do have stuff in the interface. The attached project contains two different implementations, one interface based and one skeleton based, with a few unit tests.
You need TestInsight installed and the path to DUnitX in the $(DUNITX) variable to run it.
I needed a callback for a list item (selection) and for the list itself (changed). The skeleton based implementation works as expected, but the interface based callback for the list item fails with an A/V and I don't understand why. I'm probably still doing something wrong with the generic definition.
Could someone please enlighten me what my mistake is?

Project4.zip

Share this post


Link to post

Looks like a compiler defect - when changing this declaration:

TOnMyIntfItemSelected<T: IMyIntfItem> = procedure(AItem: IMyIntfItem) of object;

the code for TMyIntfItemA<T>.Select looks like this:

List.Intf.pas.82: begin
007083E4 53               push ebx
List.Intf.pas.83: if Assigned(FOnItemSelected) then
007083E5 6683781200       cmp word ptr [eax+$12],$00
007083EA 7411             jz $007083fd
List.Intf.pas.84: FOnItemSelected(Self);
007083EC 8BD0             mov edx,eax
007083EE 85D2             test edx,edx
007083F0 7403             jz $007083f5
007083F2 83EAE8           sub edx,-$18 // this is where it turns Self into an IMyIntfItem, $18 is the offset where the interface method table pointer sits inside the object
007083F5 8BD8             mov ebx,eax
007083F7 8B4314           mov eax,[ebx+$14]
007083FA FF5310           call dword ptr [ebx+$10]
List.Intf.pas.85: end;
007083FD 5B               pop ebx
007083FE C3               ret 

but when it has the generic T parameter it looks like this:

List.Intf.pas.82: begin
007083E4 53               push ebx
List.Intf.pas.83: if Assigned(FOnItemSelected) then
007083E5 6683781200       cmp word ptr [eax+$12],$00
007083EA 740A             jz $007083f6
List.Intf.pas.84: FOnItemSelected(Self);
007083EC 8BD8             mov ebx,eax
007083EE 8BD0             mov edx,eax // here it simply passes Self
007083F0 8B4314           mov eax,[ebx+$14]
007083F3 FF5310           call dword ptr [ebx+$10]
List.Intf.pas.85: end;
007083F6 5B               pop ebx
007083F7 C3               ret 

To explain this a bit more: when putting an interface type as generic type constraint this means for the compiler that the type you put for the generic type argument not only has to be that interface type but also that it can be a class that implements this interface. TMyIntfItemA<T> does this and thus satisfies the compiler when passing it to the argument of that event handler. However, inside the event handler, it is being treated as an interface and due to the lacking const parameter the compiler inserted an IntfAddRef call which blows up as the parameter that was passed was not really an interface reference but an object reference. Putting the const parameter makes it blow up a bit later though, namely when accessing Caption.

  • Like 2
  • Thanks 1

Share this post


Link to post

Thanks Stefan,

in the debugger I saw in ItemCallback(..) that the addresses of FItem and AItem were similar but not equal, so I suspected that there was something wrong with the interface being passed around instead of the object.

 

I put a breakpoint on the first line of

procedure TestIntfItem.ItemCallback(AItem: IMyIntfItem);
begin
  Assert.IsNotNull(AItem);
  FCallbackResult := AItem.Caption;
end;

with TOnMyIntfItemSelected<T: IMyIntfItem> = procedure(AItem: T) of object; I see:

Name    Wert
Self    (TMyIntfItemA<List.Intf.IMyIntfItem>($3AA2840) as IMyIntfItem, '')
AItem    Pointer($3AA2828) as IMyIntfItem

This told me that something was wrong, but I couldn't understand what.

 

with TOnMyIntfItemSelected<T: IMyIntfItem> = procedure(AItem: IMyIntfItem) of object; I see:

Name    Wert
Self    (TMyIntfItemA<List.Intf.IMyIntfItem>($3AC38D8) as IMyIntfItem, '')
AItem    TMyIntfItemA<List.Intf.IMyIntfItem>($3AC38D8) as IMyIntfItem

That's an evil trap. Thanks for shedding light on it. So if something is wrong I have to watch out for $18 byte address offsets 😉

Edited by luebbe

Share this post


Link to post
4 hours ago, luebbe said:

I have to watch out for $18 byte address offsets 😉

Just to make sure: this offset depends on how many fields your object has and how many interfaces it implements - the IMT slots always follow after the fields. For more info take a look into "Delphi in a nutshell"

Share this post


Link to post
2 hours ago, Lars Fosdal said:

Is there a tidy way to avoid this conundrum?

Wait for a compiler fix? 🥁

Share this post


Link to post
15 hours ago, Lars Fosdal said:

Then twiddle thumbs until Delphi 12...

Looking at my unrelsolved issues from 10.4.2 and earlier.. 12 might be optimistic 🙄

  • Sad 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×