E2010 Incompatible Types, why? - delphi

I'm getting this error:
[DCC Error] JwaStrSafe.pas(2277): E2010 Incompatible types: 'PSTRSAFE_LPWSTR' and 'PSTRSAFE_LPTSTR'
The following is the relevant portion of code from JwaStrSafe.pas (from Jedi Api), I'm compiling with the symbol UNICODE defined:
type
STRSAFE_LPWSTR = PWIDECHAR;
PSTRSAFE_LPWSTR = ^STRSAFE_LPWSTR;
{$IFDEF UNICODE}
STRSAFE_LPTSTR = STRSAFE_LPWSTR;
PSTRSAFE_LPTSTR = ^STRSAFE_LPTSTR;
{$ELSE}
...
{$ENDIF}
...
//function declaration
function StringCchCopyExW(
{__out_ecount(cchDest)}pszDest : STRSAFE_LPWSTR;
{__in}cchDest : size_t;
{__in}const pszSrc : STRSAFE_LPCWSTR;
{__deref_opt_out_ecount(pcchRemaining^)}ppszDestEnd : PSTRSAFE_LPWSTR;
{__out_opt}pcchRemaining : PSize_t;
{__in}dwFlags : Cardinal) : HRESULT; stdcall; forward; external;
...
//var passed to function
ppszDestEnd : PSTRSAFE_LPTSTR;
...
{$IFDEF UNICODE}
result := StringCchCopyExW(pszDest, cchDest, pszSrc, ppszDestEnd, pcchRemaining, dwFlags);
{$ELSE}
result := StringCchCopyExA(pszDest, cchDest, pszSrc, ppszDestEnd, pcchRemaining, dwFlags);
{$ENDIF}
I get the error on the call of StringCchCopyExW, on parameter ppszDestEnd.
Looking at the type definition I understand that PSTRSAFE_LPTSTR is a pointer type to STRSAFE_LPTSTR which is just an alias of STRSAFE_LPWSTR, why are PSTRSAFE_LPTSTR and PSTRSAFE_LPWSTR incompatible?
Solution
Thanks to David's explanation I replaced
PSTRSAFE_LPTSTR = ^STRSAFE_LPTSTR;
with
PSTRSAFE_LPTSTR = PSTRSAFE_LPWSTR;
now the code compiles without errors.
Thanks

I can reproduce this easily enough in XE2, and I imagine it will behave the same in all other versions. To make it simpler I've cut it down to this:
program PointerTypeCompatibility;
{$APPTYPE CONSOLE}
type
A = Integer;
B = Integer;
var
ptA: ^A;
ptB: ^B;
begin
ptA := ptB;
end.
This also produces E2010. However, if you enable the type-checked pointers option, then the code compiles successfully. In fact the documentation of that compiler options states:
In the {$T-} state, distinct pointer types other than Pointer are incompatible (even if they are pointers to the same type). In the {$T+} state, pointers to the same type are compatible.
Thanks to Ken White for pointing me at the useful help topic Type Compatibility and Identity. The pertinent extracts are that types T1 and T2 are assignment compatible if:
T1 and T2 are compatible pointer types.
The documentation also states that types are type compatibile if:
Both types are (typed) pointers to the same type and the {$T+} compiler directive is in effect.
So this documents the observed behaviour and leads me to this example:
program PointerTypeCompatibilityTake2;
{$APPTYPE CONSOLE}
{$TYPEDADDRESS OFF}
var
P1,P2: ^Integer;
P3: ^Integer;
begin
P1 := P2;//compiles
P1 := P3;//E2008 Incompatible types
end.
So, to summarise:
When type-checked pointers is disabled, pointers are assignment compatible if the pointers are of the same type.
When type-checked pointers is enabled, pointers are assignment compatible if the pointers point to the the same type.
I have to confess to being ignorant of the history and reasoning behind the type-checked pointer setting, so I can't offer any explanation for why the compiler is the way it is.

Related

Incompatible types for different pointer types

I am using quick report 6 in Delphi 10.2. When I add quickreport source path to library paths I am getting Incompatible type errors on qrpdffilt.pas.
Var
P: ^ pos2table;
Buff: array of ansichar;
d: dword;
RGBCol:TRGBColor;
PColor: TColor;
Pos2table is of type packed array
Incompatible types issue comes for following lines
P:=#Buff[d];
RGBCol:=pcolor;
Any solution?
P := #Buff[d]; is assigning an ^AnsiChar pointer to a ^pos2table pointer, so of course the compiler will complain since they are pointers to different types, but only if you have type-checked pointers enabled, in which case you need to use a typecast to resolve it, eg:
type
ppos2table = ^pos2table;
var
P: ppos2table;
Buff: array of ansichar;
...
P := ppos2table(#Buff[d]);
RGBCol:=pcolor; is trying to assign a TColor (an integer type, not a pointer type) to a TRGBColor (presumably a record type). There is no standard implicit conversion between them, so the compiler complains about this. You can use a pointer typecast to resolve this, too:
type
PRGBColor = ^TRGBColor;
var
...
RGBCol: TRGBColor;
PColor: TColor;
...
RGBCol := PRGBColor(#pcolor)^;

How to resolve Delphi error: Incompatible types: 'PWideChar' and 'Pointer'

I have this piece of code from Delphi 7:
var
lpRgnData: PRgnData;
PC: PChar;
PR: PRect;
...
PC := #(lpRgnData^.Buffer[0]);
In Delphi XE4 it gives the following compile error:
Incompatible types: 'PWideChar' and 'Pointer'
How should this code be updated to work correctly in XE4?
Thanks
Whether or not this compiles depends upon the setting of the type-checked pointers option. You clearly have enabled that option which is an excellent decision. Doing so results in stricter type checking.
With type-checked pointers disabled, your code does compile. With type-checked pointers enabled, your code does not compile, which is what you want because your code is not valid.
Now, on to the types in question. They are defined in the Windows unit like this:
type
PRgnData = ^TRgnData;
{$EXTERNALSYM _RGNDATA}
_RGNDATA = record
rdh: TRgnDataHeader;
Buffer: array[0..0] of Byte;
Reserved: array[0..2] of Byte;
end;
TRgnData = _RGNDATA;
{$EXTERNALSYM RGNDATA}
RGNDATA = _RGNDATA;
The benefit of using type-checked pointers is that the compiler can tell you that what you are doing is not valid. It knows that lpRgnData^.Buffer[0] has type Byte and so #(lpRgnData^.Buffer[0]) has type ^Byte. And it knows that is not compatible with PChar which is an alias for PWideChar, that is ^WideChar.
Fix your code by changing the type of PC to ^Byte or PByte.

Will Delphi compiler always fail when compiling a module with conditional-based type mismatch?

For practical example, suppose ModuleA have some type varying on conditional compilation:
unit ModuleA;
interface
type
{ explicit character width }
PASTR = type PAnsiChar;
PWSTR = type PWideChar;
{ imlicit character width }
PTSTR = {$IFDEF UNICODE}PWSTR{$ELSE}PASTR{$ENDIF};
{ ... }
And now the set of functions in ModuleB is depending on the type declared in ModuleA:
unit ModuleB;
{ ... }
implementation
uses ModuleA;
{ explicit }
function FuncA(Arg: PASTR): Integer;
begin
{ do something with 1 byte char }
end;
function FuncW(Arg: PWSTR): Integer;
begin
{ do something with 2 byte char }
end;
{ implicit - will compiler safeguard against mismatching UNICODE define? }
function Func(Arg: PTSTR): Integer;
begin
{ map to explicit one }
Result := {$IFDEF UNICODE}FuncW(Arg){$ELSE}FuncA(Arg){$ENDIF};
end;
Now, suppose ModuleA has been compiled with UNICODE symbol defined and DCU file has been generated. And then ModuleB has been compiled using DCU information only without UNICODE symbol (or vice versa case - ModuleA.dcu generated as ANSI, and then there is an attempt to use it in Unicode ModuleB).
Will Delphi compiler always fail when compiling a module with such conditional-based type mismatch? With type keyword and without it? Is there any version specific behaviour?
As a bonus: I'm interested in the same for Free Pascal.
The compiler will fail to compile the program in that scenario. Both in Delphi and FPC.
One way to think about it is to expand the conditional code. Once written that way it becomes much clearer what the compiler will do. And this is the right mental model because that is how the compiler processes conditional code. It simply does not see the code that is in conditional branches that are not active.
Consider these two units:
unit Unit1;
// expanded with UNICODE defined
interface
type
PASTR = type PAnsiChar;
PWSTR = type PWideChar;
PTSTR = PWSTR;
implementation
end.
unit Unit2;
// expanded with UNICODE undefined
interface
implementation
uses
Unit1;
function FuncA(Arg: PASTR): Integer;
begin
end;
function FuncW(Arg: PWSTR): Integer;
begin
end;
function Func(Arg: PTSTR): Integer;
begin
Result := FuncA(Arg);
end;
end.
The definition of PTSTR is found in Unit1 and it is an alias to PWSTR. Hence the call to FuncA will not compile.
The use of type to make distinct types changes nothing either, because PAnsiChar and PWideChar are already incompatible.
Yes and no, you could break it if you introduced a DEF or UNDEF of the UNICODE symbol, effectively overriding the directive you pass in when compiling.
Not sure why you would do something that daft, though.
Can't think of any version based difference, not breaking would be a catastrophic bug.

Why does Format reject procedure address arguments starting with XE4

Consider this program:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
procedure Foo;
begin
end;
type
TProcedure = procedure;
const
FooConst: TProcedure = Foo;
var
FooVar: TProcedure = Foo;
P: Pointer;
{$TYPEDADDRESS ON}
begin
P := #Foo;
Writeln(Format('%p', [P]));
Writeln(Format('%p', [#FooConst]));
Writeln(Format('%p', [#FooVar]));
Writeln(Format('%p', [#Foo]));
Readln;
end.
This program compiles and runs on XE3 and produces the following output:
00419FB8
00419FB8
00419FB8
00419FB8
On XE4 and later the program fails to compile, with error messages on both of these lines:
Writeln(Format('%p', [#FooConst]));
Writeln(Format('%p', [#FooVar]));
[dcc32 Error] E2250 There is no overloaded version of 'Format' that can be called
with these arguments
On XE4, XE5 and XE6, the program compiles when $TYPEDADDRESS is switched off. On XE7, the program fails to compile irrespective of the setting of $TYPEDADDRESS.
Is this a compiler bug? Or am I using incorrect syntax to obtain the address of a procedure?
I believe that this is a compiler bug and have submitted a QC report: QC#127814.
As a work around you can use either of the following:
Use addr() rather than the # operator.
Cast #FooVar or #FooConst to Pointer, e.g. Pointer(#FooVar).
I think that the behaviour of the new compiler XE7 is more consistent with the specification and the error need to be shown in this case, since the {$TYPEDADDRESS ON} enforce the # operator to return a typed pointer and the Format function instead gets as input an untyped generic pointer.
Since the the purpose of the {$TYPEDADDRESS ON} is encouraging careful use of pointers, catching at compile time unsafe pointer assignments, it is right that if the function expects a generic untyped pointer(and in this case make sense because the function purpose is to print the address of it - so no need to have typed pointer to retrieve its address), the compiler will catch an error in the case of a typed pointer is passed, the behaviour is consistent with the specification.
I think that in this case the right way to go(based on the documentation) would be :
Writeln(Format('%p', [Addr(FooConst)]));
Writeln(Format('%p', [Addr(FooVar)]));
since the Addr function always returns an untyped Pointer that is what the Format with %p expects and needs.
What I assume is that in previous versions the compiler, in a case like this one, used to perform an automatic cast : Pointer(#FooConst) , but it doesn't make too much sense because of the {$TYPEDADDRESS ON} directive .

Delphi - Interface inheritance with generics

I am currently stuck with a compiling error, no one in our company can help and I am sadly not finding the correct search patterns for SO or google.
As code I am using 2 Interfaces, inherited and 2 Classes, inherited.
The following code reproduces the error:
program Project22;
{$APPTYPE CONSOLE}
type
IStorageObject = interface(IInterface)
end;
TObjectStorage<T: IStorageObject> = class(TObject)
end;
IKeyStorageObject<TKey> = interface(IStorageObject)
end;
TKeyObjectStorage<TKey, T: IKeyStorageObject<TKey>> = class(TObjectStorage<T>)
end;
TImplementingClass<TKey> = class(TInterfacedObject, IKeyStorageObject<TKey>)
end;
begin
TKeyObjectStorage<Integer, TImplementingClass<Integer>>.Create;
end.
The compiler error for 'TKeyObjectStorage' is:
[DCC Error] Project22.dpr(11): E2514 Type parameter 'T' must support interface 'IStorageObject'
What I think is, that the compiler is not recognizing that Parameter T of the Class 'TKeyObjectStorage' correctly.
It should be correct, since the wanted Type 'IKeyStorageObject' has the parent type IStorageObject.
Why is this not working? What am I doing wrong? Is this not possible in Delphi?
Update
The original question had a problem which I identified (see below). However, the fix I describe there is fine for XE3 and later, but that program below does not compile in XE2. Thus I conclude that this is an XE2 generics compiler bug.
Anyway, here's a workaround for Delphi XE2:
{$APPTYPE CONSOLE}
type
IStorageObject = interface(IInterface)
end;
TObjectStorage<T: IStorageObject> = class(TObject)
end;
IKeyStorageObject<TKey> = interface(IStorageObject)
end;
TKeyObjectStorage<TKey; T: IKeyStorageObject<TKey>, IStorageObject> = class(TObjectStorage<T>)
end;
TImplementingClass<TKey> = class(TInterfacedObject, IStorageObject, IKeyStorageObject<TKey>)
end;
begin
TKeyObjectStorage<Integer, TImplementingClass<Integer>>.Create;
end.
Original answer
It would have been better if you had provided a complete program that exhibited the compiler error. You need to attempt to instantiate an object to see that error.
But, I think I've reproduced your problem. So I believe that the issue is that this code:
TKeyObjectStorage<TKey, T: IKeyStorageObject<TKey>> = ...
applies the generic constraint to both TKey and T. Now, clearly you only want the constraint to apply to T so you'll need to write:
TKeyObjectStorage<TKey; T: IKeyStorageObject<TKey>> = ...
Here's a short program that compiles following the change in Delphi XE3:
{$APPTYPE CONSOLE}
type
IStorageObject = interface(IInterface)
end;
TObjectStorage<T: IStorageObject> = class(TObject)
end;
IKeyStorageObject<TKey> = interface(IStorageObject)
end;
TKeyObjectStorage<TKey; T: IKeyStorageObject<TKey>> = class(TObjectStorage<T>)
end;
TImplementingClass<TKey> = class(TInterfacedObject, IKeyStorageObject<TKey>)
end;
begin
TKeyObjectStorage<Integer, TImplementingClass<Integer>>.Create;
end.
This is quite a nuance, the changing of a comma to a semi-colon. Programming by significant punctuation is never much fun. That said, you are familiar with the difference between commas and semi-colons in formal parameter lists and so it should not come as too much of a surprise to see the same distinction drawn here.
The documentation does cover this mind you:
Multiple Type Parameters
When you specify constraints, you separate multiple type parameters by
semicolons, as you do with a parameter list declaration:
type
TFoo<T: ISerializable; V: IComparable>
Like parameter declarations, multiple type parameters can be grouped
together in a comma list to bind to the same constraints:
type
TFoo<S, U: ISerializable> ...
In the example above, S and U are both bound to the ISerializable
constraint.

Resources