yonojoy 2 Posted December 16, 2021 (edited) Consider the following code: type IBaseBroker = interface ['{FE71BBA5-A6F8-4E75-894E-54F830023E99}'] end; IFooBroker = interface(IBaseBroker) function GetMsg(): string; end; TIBase<I: IBaseBroker> = class private FBroker: I; protected property Broker: I read FBroker; public constructor Create(ABroker: I); virtual; end; TIBaseExt<I: IBaseBroker> = class (TIBase<I>) public constructor Create(ABroker: I); override; end; TIFoo = class (TIBaseExt<IFooBroker>) public constructor Create(ABroker: IFooBroker); override; procedure Test; end; TIBaseBroker = class(TInterfacedObject, IBaseBroker) end; TIFooBroker = class(TIBaseBroker, IFooBroker) //no AV with ,IBaseBroker! protected function GetMsg(): string; end; implementation procedure TForm1.Button1Click(Sender: TObject); var Broker: IFooBroker; Foo: TIFoo; begin Broker := TIFooBroker.Create(); Foo := TIFoo.Create(Broker); try //AV here: Foo.Test; finally Foo.Free; end; end; constructor TIFoo.Create(ABroker: IFooBroker); begin inherited Create(ABroker); end; procedure TIFoo.Test; begin //Access violation: ShowMessage(Broker.GetMsg()); end; constructor TIBase<I>.Create(ABroker: I); begin FBroker := ABroker; end; constructor TIBaseExt<I>.Create(ABroker: I); var BaseBroker: IBaseBroker; begin //silly cast: if not Supports(ABroker, IBaseBroker, BaseBroker) then raise Exception.Create('...'); // I thought the following line should not compile for TIFoo = TIBaseExt<IFooBroker>: inherited Create(BaseBroker); end; function TIFooBroker.GetMsg: string; begin Result := 'OK'; end; I thought Create(I: IBaseBroker) should not compile, once I is instantiated with IFooBroker. But it compiles and I get an AV (at least with XE4). Or do I miss something? Edited December 16, 2021 by yonojoy Share this post Link to post
Anders Melander 1782 Posted December 16, 2021 I can't really follow your example but I noticed that you haven't assigned IFooBroker a GUID. Is that by design? Also, are you aware that inheriting one interface from another is just a convenience that copies the declaration from the base interface into the derived interface. There's no "interface polymorphism". Share this post Link to post
Der schöne Günther 316 Posted December 16, 2021 Why are you casting it? It makes no sense. In a TIFoo, FBroker is already of type IFooBroker. Your constructor of TIBaseExt<I> can just look like this constructor TIBaseExt<I>.Create(ABroker: I); begin inherited Create(ABroker); end; and therefore is not necessary at all. After removing the unnecessary casting, your code runs fine with no further changes. Share this post Link to post
yonojoy 2 Posted December 17, 2021 I know the cast is senseless. This was my try to break down a real world bug to, what I think, might be a compiler bug. Essentially I have a class definition with TIBaseExt<I: IBaseBroker>.Create(ABroker: I) and I can call this Create constructor with IBaseBroker even if I is instantiated with IFooBroker. In the end I should have TIBaseExt<IFooBroker>.Create(ABroker: IFooBroker) and I can call this with an IBaseBroker interface and the compiler wont complain. The cast was the bug in our code and I would have expected the compiler to error on it, but instead it compiled and I had to track the access violations. I modified the calling code to make this more clear: procedure TForm1.Button1Click(Sender: TObject); var BaseBroker: IBaseBroker; Broker: IFooBroker; Foo: TIFoo; begin Broker := TIFooBroker.Create(); if not Supports(Broker, IBaseBroker, BaseBroker) then raise Exception.Create('...'); //Here the compiler will complain: E2010: Incompatible types 'IFooBroker' and 'IBaseBroker' TIBaseExt<IFooBroker>.Create(BaseBroker); //This Create will lead to the same call, but the compiler wont complain: Foo := TIFoo.Create(Broker); try Foo.Test; finally Foo.Free; end; end; @Anders Melander A GUID for IFooBroker wont change anything. IFooBroker and IBaseBroker are different interfaces and the compiler should IMO not allow a call with IBaseBroker if I is instantiated with IFooBroker. Share this post Link to post
Anders Melander 1782 Posted December 17, 2021 13 minutes ago, yonojoy said: IFooBroker and IBaseBroker are different interfaces Yes, exactly. I just wanted to be sure that you were aware of this. Many people misunderstand what interface inheritance is. I still can't follow your example but from your description it does sound like there's a bug. Since you're not doing any hard type casts the compiler should see the problem (whatever it is) and not produce code that leads to an AV. Share this post Link to post
yonojoy 2 Posted December 17, 2021 2 hours ago, Anders Melander said: I still can't follow your example ... That was the most simple example I could find, that triggers the problem. I have a class hierarchy: TIBase<I> TIBaseExt<I> = class(TIBase<I>) TIFoo = class(TIBaseExt<IFooBroker>) TIBase<I> has a local variable Broker of type I. With my code one can assign a variable of type IBaseBroker to Broker even if Broker is of type IFooBroker (via TIBase<IFooBroker>). What I really wanted to know is, if I should report this to QP, especially if this problem still exists in actual Delphi versions (I only have XE4 available here). Share this post Link to post
Anders Melander 1782 Posted December 17, 2021 59 minutes ago, yonojoy said: With my code one can assign a variable of type IBaseBroker to Broker even if Broker is of type IFooBroker IMO that's a bug since the two interfaces are distinct. I don't have time to investigate if the problem exists in newer versions right now. Share this post Link to post
Der schöne Günther 316 Posted December 17, 2021 Put a breakpoint on your constructor TIBase<I>.Create(ABroker: I); begin FBroker := ABroker; end; ABroker is of type IFooBroker. Which absolutely makes sense, because for TIFoo, I is of type IFooBroker. However, you stuffed a reference of type IBaseBroker in there. The compiler shouldn't have let you. Share this post Link to post
Uwe Raabe 2057 Posted December 17, 2021 Note, that the implementation of TIBase<I> can be in another unit, where neither IFooBroker is known, nor that TIFoo is a type instance for IFooBroker. Nevertheless that unit has to be compiled before the unit containing IFooBroker and TIFoo. The compiler should only allow anything of type I as a parameter for inherited Create. That will rule out to grab any interfaces via Supports or AS constructs. Please file a bug report. Share this post Link to post
yonojoy 2 Posted December 17, 2021 @Der schöne Günther @Uwe Raabe OK. So can I assume that this behaviour was reproducable in Delphi 10 / Delphi 11 ? Share this post Link to post
Der schöne Günther 316 Posted December 20, 2021 I tested in Delphi 10.0 Seattle where it was reproducible. Share this post Link to post
yonojoy 2 Posted December 20, 2021 Ok. I filed a bug report: https://quality.embarcadero.com/browse/RSP-36645 1 Share this post Link to post
Stefan Glienke 2002 Posted January 4, 2022 The supports is misleading. The generic type parameter I is contrained to be of IBaseBroker and thus ABroker can directly be assigned to BaseBroker (as a side note in that case you would not get an AV because then the same interface pointer that points to the IMT for IFooBroker would be passed down satisfying the later call to GetMsg). However the bug is that the inherited Create call which has I as parameter (which is in this case IFooBroker) accepts the base type that it was constrained on. Here is the issue a bit more condensed - and showing it does not only apply to interfaces: type Base = class end; Child = class(Base) end; X<T: Base> = class procedure Test(obj: T); end; Y<T: Base> = class(X<T>) procedure Test(obj: T); end; procedure X<T>.Test(obj: T); begin end; procedure Y<T>.Test(obj: T); var b: Base; begin b := obj; // obj IS a Base because T is constrained to be a Base inherited Test(b); // this MUST NOT compile because the parameter is T and Base is not a T end; 2 Share this post Link to post
Anders Melander 1782 Posted January 4, 2022 10 minutes ago, Stefan Glienke said: Here is the issue a bit more condensed - and showing it does not only apply to interfaces: Very nice. That actually makes it very clear what the problem is - but isn't there something missing? You've declared Child but not actually referenced it anywhere. Maybe you meant to write: Y<T: Child> = class(X<T>) ... Share this post Link to post
Stefan Glienke 2002 Posted January 4, 2022 (edited) 45 minutes ago, Anders Melander said: Very nice. That actually makes it very clear what the problem is - but isn't there something missing? You've declared Child but not actually referenced it anywhere. Maybe you meant to write: Y<T: Child> = class(X<T>) ... While writing the code I figured that for the issue to show it does not need anything that really inherits from the type the generic is constrained on because the issue is within the generic itself. I just did not remove the Child declaration. It's just there to show that you might instantiate the generic type with anything inheriting from Base that satisfies the constraint. If I had to guess I would say that when inheriting Y from X the compiler does not put the actual T into the generic type parameter but just the type it is constrained on or something - at least for the parameter checking. Edited January 4, 2022 by Stefan Glienke Share this post Link to post