Search code examples
delphigenericsdelphi-xe

Delphi generic class usage compilation error


I'm writing some utility code for an old Delphi XE codebase; in order to keep things simpler and safer I've created a method meant to wrap a type-specific TProc<TReq> (where TReq is a generic class type) passed via parameters in a more broad TProc<TObject> that should be fed to a third-party component later on, along with TClass(TReq) and an additional string argument:

type
  TREvoHostConnectionOptions = record
    // [...]
    procedure OnPush<TReq:class>(const pushMethod: string; const action: TProc<TReq>);
  end;

// [...]

procedure TREvoHostConnectionOptions.OnPush<TReq>(const pushMethod: string; const action: TProc<TReq>);
    var
      rec: TRPushHandler;
    begin
      rec.PushMethod := pushMethod;
      rec.PushModel := TClass(TReq);
      rec.Handler :=
        procedure(reqRawModel: TObject)
          var
            reqModel: TReq;
          begin
            // Conversione modello richiesta
            reqModel := reqRawModel as TReq;
            if Assigned(reqRawModel) and not Assigned(reqModel) then
              raise EEvoHostException.Create(Format('Impossibile convertire il modello di tipo %s in %s.', [reqRawModel.ClassName, TClass(TReq).ClassName]));
            // Azione
            if Assigned(action) then
              action(reqModel);
          end;
      PushHandlers.Add(rec);
    end;

The previous method compiles successfully and if invoked like this, works as intended (although, having TObject as the generic type defeats the purpose of the method):

opts.OnPush<TObject>('Test', procedure (reqModel: TObject) begin (* ... *) end);

However, if in the testing form unit I invoke it with a specifically crafted model class:

  type
    TTestModel = class(TObject)
      strict private
        _a, _b: string;
      public
        property A: string read _a write _a;
        property B: string read _b write _b;
    end;

I get the following compiler error in a completely irrelevant line (and completely different and totally unrelated method) in the calling unit:

[DCC Error] WndMain.pas(96): E2010 Incompatible types: 'TTestModel' and 'TObject'

* The displacement only occurs with this specific error, if I introduce an artificial syntax error anywhere else in the same file it gets reported at the correct line.


Any thoughts? Is this a compiler bug, and if so is there any way to get around it? Unfortunately I can't remove the :class constraint on the method because otherwise the TClass(TReq) conversion that happens inside the method raises (logically) another compilation error about TReq not being a constrained class or interface type.


Solution

  • After further investigation, the problem seems to be caused by the as conversion in the method, even though it was reported in the wrong file.

    Changing the method like this seems to have solved it:

    procedure TREvoHostConnectionOptions.OnPush<TReq>(const pushMethod: string; const action: TProc<TReq>);
        var
          rec: TRPushHandler;
        begin
          rec.PushMethod := pushMethod;
          rec.PushModel := TClass(TReq);
          rec.Handler :=
            procedure(reqRawModel: TObject)
              var
                reqModel: TReq;
              begin
                // Conversione modello richiesta
                if Assigned(reqRawModel) and not reqRawModel.ClassType.InheritsFrom(rec.PushModel) then
                  raise EEvoHostException.CreateFmt('Impossibile convertire il modello di tipo %s in %s.', [reqRawModel.ClassName, rec.PushModel.ClassName]);
                // Azione
                if Assigned(action) then
                  action(TReq(reqModel));
              end;
          PushHandlers.Add(rec);
        end;