Parameter of type X must support interface Y

2019-07-07 04:33发布

问题:

I have a setup like so :

IBuilder = interface(IInvokable)
end;

IBuilder<T: IBuilder; TOut : TWinControl> = interface(IInvokable)
end;

TBuilder<T: IBuilder; TOut : TWinControl> = class(TInterfacedObject, IBuilder, IBuilder<T, TOut>)
end;

TBuilder = class(TBuilder<TBuilder, TWinControl>)
end;

This kind of structure allows me to build a sugar syntax like so :

TBuilder<T : IBuilder; TOut : TWinControl> = class(TInterfacedObject, IBuilder, IBuilder<T, TOut>)

  function Output : TOut;
  function Name(aName : string) : T;
  function Left(aLeft : Integer) : T;
  function Top(aTop : Integer) : T;

end;

// ... later

TBuilder.Create().Left(10).Top(5).Name('ABC'); // Nice one liner

The problem is that I get a compilation error, saying that

E2514 The type parameter TBuilder must support interface 'IBuilder'.

This is probably due to the typed constraint T: IBuilder present on the interface, even though TBuilder does support IBuilder (trough it's ancestor).

Can anyone please direct me on how to get around this?

Though, I cannot use TBuilder = class(TBuilder<IBuilder, TObject>)

回答1:

This can't be done. You're essentially trying to do this :

  IBar = interface(IInterface) end; 

  TFoo<T : IBar> = class(TObject, IBar) end;

  TBar = TFoo<TBar>;

Which generates error

E2086 Type 'TBar' is not yet completely defined

Without the interface dependence you can write this as

 TBar = class(TFoo<TBar>) end;

making it a true descendent and not just an alias. This could normally resolve the type, but the interface dependence is forcing the compiler to ask the question : Does TBar support IBar?

If you think about it, this works out as :

TBar = TFoo<TBar>   {TBar support IBar?}  
             |
             TBar = TFoo<TBar>... {ok, TBar support IBar?}
                          |
                         TBar = TFoo<TBar> {ok, TBar support IBar?}
                                      |
                                      {...turtles all the way down}

You're asking the compiler to solve an infinite recursion problem. It cannot do this.



回答2:

You can fix this by changing the return type of your methods, and excluding the recursive type parameter.

interface
type
  //IBuilder = interface(IInvokable)
  //end;  //I don't think you need this

  IBuilder<TOut : TWinControl> = interface(IInvokable)
    function Output : TOut;
    function Name(const aName : string) : IBuilder<TOut>;
    function Left(aLeft : Integer) : IBuilder<TOut>;
    function Top(aTop : Integer) : IBuilder<TOut>;
  end;

 TFactory<TOut: TWinControl> = record
   class function New: IBuilder<TOut>; static;
 end;

implementation
type    
  //Put the actual class in the implementation
  TBuilder<TOut : TWinControl> = class(TInterfacedObject, IBuilder<TOut>)
     //see interface
  end;

You normally use this like so:

var 
  MyButton: IBuilder<TButton>;
begin  
  MyButton:= TFactory<TButton>.New.Left(10).Top(5).Name('ABC');

If you're using interface then you should never work with the class, always interact with the interface exclusively. By moving the class definition in the implementation you enforce this. To compensate you add a factory method in the interface.

In this case it has to be a record, because you cannot (yet) have generic stand-alone methods.

class function TFactory<TOut>.New: IBuilder<TOut>;
begin
  Result:= TBuilder<TOut>.Create;
end;