8
votes

While porting some code from D2007 to XE2 I got a compiler error I can't understand. Please see the following sample:

procedure TForm1.FormPaint(Sender: TObject);
var
  c: Char;
  pc: PChar;
  r: TRect;
begin
  c := '1';
  pc := @c;
  r := Bounds(100, 100, 100, 100);
  DrawText(Canvas.Handle, pc, 1, r, DT_SINGLELINE or DT_NOCLIP); //1
{$TYPEDADDRESS OFF}
  DrawText(Canvas.Handle, @c, 1, r, DT_SINGLELINE or DT_NOCLIP); //2
{$TYPEDADDRESS ON}
  DrawText(Canvas.Handle, @c, 1, r, DT_SINGLELINE or DT_NOCLIP); //3
  DrawText(Canvas.Handle, PChar(@c), 1, r, DT_SINGLELINE or DT_NOCLIP); //4
end;

D2007 compiles that without a problem. The XE2 compiler rejects the line marked //3 with

[DCC Fehler] Unit1.pas(38): E2010 Inkompatible Typen: 'string' und 'Pointer'

I guess that is due to the newly added DrawText overloads accepting Delphi strings.

Can you explain that error? It's no big problem as I have a workaround (explicit casting), but I'm curious. Is the error still present in later Delphi versions?

Edit: I'm asking whether there is an error in the compiler, not for an explanation why it is there. It might well be that I overlooked a valid reason for the compiler to reject my code.

1
I can confirm the same compiler error message in XE6 upd 1. - LU RD
The error is quite odd, but I doubt anyone outside the compiler team can explain it. Sadly Delphi overloading resolution is not fully documented. I suppose that somebody may be able to reverse engineer a full description of the overloading resolution. - David Heffernan
@David, see my edit. I tried to clarify what I'm asking. :-) - Uli Gerhardt
I don't even know how @c is treated in //3, or how the overload resolution is meant to work. So, if we cannot know how the overload resolution is meant to work, it's hard to judge whether this is a compiler bug or not. I might be wrong, but whenever I've looked at documentation for overload resolution I have found it to be incompletely described. - David Heffernan
It seems easy to me: c is a Char, so @c is a PChar, so choose the overload that explicitly takes a PChar. I don't really see how line //3 is different from //1 or //4 regarding overload resolution. //2 is different as @c is an untyped pointer there. - Uli Gerhardt

1 Answers

7
votes

This doesn't seem to be a normal overload resolution situation. DrawText is defined twice as :

function DrawText(hDC: HDC; 
                  lpString: PWideChar; 
                  nCount: Integer;
                  var lpRect: TRect; 
                  uFormat: UINT): Integer; external user32 name 'DrawTextW';

function DrawText(hDC: HDC; 
                  const lpString: UnicodeString; 
                  nCount: Integer;
                  var lpRect: TRect; uFormat: UINT): Integer;
begin
  Result := Winapi.Windows.DrawText(hDC, 
                                    PWideChar(lpString), 
                                    nCount, 
                                    lpRect, 
                                    uFormat);
end;

With {$TYPEDADDRESS OFF} it seems that a ^Char is interpreted by the compiler as an untyped pointer that is never compatible with a declared type of PChar while @c does seem to resolve to a PChar ok. This seems at odds with the notion that {$TYPEDADDRESS OFF} is meant to make all pointers type-agnostic. It seems that PChar and ^Char are somehow treated differently in the compiler than other pointers.

With {$TYPEDADDRESS ON} both @c and ^Char become equivalent, but curiously are accepted as arguments so long as there is no overload resolution to sort out.

In both cases it seems that overload resolutions are finalized before type-compatibility is fully established. I'm not sure I'd call it a bug, though... it seems like behaviour that would be tricky to change without causing problems.

SSCCE

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

{$DEFINE OVLD}

{$IFDEF OVLD}
procedure Test(s:string); overload;
begin
end;
{$ENDIF}
procedure Test(x:PChar); {$IFDEF OVLD}overload; {$ENDIF}
begin
end;

var
  c : Char;
  pc : ^Char;
begin
  {$TYPEDADDRESS OFF}
  Test(@c);
  Test(pc);    //OVLD - Incompatible types : 'string'-'pointer'
               //No OVLD - Incompat. types : 'PWideChar'-'pointer'
  {$TYPEDADDRESS ON}
  Test(@c);    //OVLD - Incompatible types : 'string'-'pointer'
               //No OVLD - OK
  Test(pc);    //OVLD - Incompatible types : 'string'-'pointer'
               //No OVLD - OK
end.