luebbe 26 Posted August 26, 2021 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... 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. 1 Share this post Link to post
darnocian 86 Posted August 26, 2021 (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 August 26, 2021 by darnocian Share this post Link to post
Lars Fosdal 1792 Posted August 26, 2021 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. 1 1 Share this post Link to post
luebbe 26 Posted August 26, 2021 Thanks Lars, once again this is proof, that T(ea) makes you happy... 1 Share this post Link to post
Lars Fosdal 1792 Posted August 26, 2021 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
luebbe 26 Posted August 26, 2021 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
Lars Fosdal 1792 Posted August 26, 2021 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; 1 Share this post Link to post
luebbe 26 Posted August 26, 2021 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
Lars Fosdal 1792 Posted August 26, 2021 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
luebbe 26 Posted August 26, 2021 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
David Heffernan 2345 Posted August 26, 2021 I don't understand why you would use TMyList<T:IMyIntf> rather than TMyList<T> here 1 Share this post Link to post
Lars Fosdal 1792 Posted August 26, 2021 Would methods exposed by IMyIntf be visible for references to T in the generic code without that constraint? Share this post Link to post
David Heffernan 2345 Posted August 26, 2021 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
Lars Fosdal 1792 Posted August 27, 2021 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
David Heffernan 2345 Posted August 27, 2021 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
luebbe 26 Posted September 6, 2021 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
Stefan Glienke 2006 Posted September 7, 2021 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. 3 1 Share this post Link to post
luebbe 26 Posted September 7, 2021 (edited) 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 September 7, 2021 by luebbe Share this post Link to post
Stefan Glienke 2006 Posted September 7, 2021 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
Lars Fosdal 1792 Posted September 8, 2021 Is there a tidy way to avoid this conundrum? Share this post Link to post
Stefan Glienke 2006 Posted September 8, 2021 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
Lajos Juhász 293 Posted September 8, 2021 1 minute ago, Stefan Glienke said: Wait for a compiler fix? 🥁 Before that open a ticket at Embarcadero. Share this post Link to post
Lars Fosdal 1792 Posted September 8, 2021 Then twiddle thumbs until Delphi 12... Share this post Link to post
Vincent Parrett 750 Posted September 9, 2021 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 🙄 2 Share this post Link to post