12
votes

In Pascal there are two kinds of type declarations:

  • type aliases: type NewName = OldType
  • type creation: type NewType = type OldType

The former is just creating convenient shorthand, like typedef in C. The aliases are compatible one to another and to their original type. The created types are intentionally incompatible and cannot be mixed without explicit and unsafe by definition typecast.

var
  nn: NewName; nt: NewType; ot: OldType;
...
  nn := ot; // should work
  nt := ot; // should break with type safety violation error.

  nt := NewType(ot); // Disabling type safety. Should work even if 
  // it has no sense semantically and types really ARE incompatible.

Those are Pascal basics as i understand them.

Now let's look at one certain type and two its aliases:

  • System.Types.TStringDynArray = array of string;
  • System.TArray<T> = array of T;
    • in particular that means TArray<string> = array of string; by definition.

Now let's take function returning the former type alias and feed its result to the function expecting the latter one:

uses Classes, IOUtils;

 TStringList.Create.AddStrings(
    TDirectory.GetFiles('c:\', '*.dll') );

 TStringList.Create.AddStrings(
     TArray<string>( // this is required by compiler - but why ???
         TDirectory.GetFiles('c:\', '*.dll') ) );

1st snippet would not compile due to types violation. 2nd one happily compiles and works, but is fragile towards future type changes and is redundant.

QC tells that compiler is right and the RTL design is wrong. http://qc.embarcadero.com/wc/qcmain.aspx?d=106246

WHY compiler is right here ? Why those aliases are incompatible ? Even the very manner RTL was designed suggests that they were deemed compatible!

PS. David suggested even simplier example, without using TArray<T>

 type T1 = array of string; T2 = array of string;

 procedure TForm1.FormCreate(Sender: TObject);
  function Generator: T1;
    begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
  procedure Consumer (const data: T2);
    begin
      with TStringList.Create do 
      try
        AddStrings(data);
        Self.Caption := CommaText;
      finally
        Free;
      end;
    end;
  begin
    Consumer(Generator);
  end;

Same gotcha without explanation...

PPS. There are a number of doc refs now. I want to stress one thing: while this restriction might be indirectly inherited from Pascal Report of 1949, today is 2012 and Delphi used very differently from school labs of half-century ago. I named few BAD effects of keeping this restrictions, and yet did not saw any good one.

Ironic thing, that this restricion may be lifted without breaking rules of Pascal: in Pascal there is no such non-strict beast as Open Arrays and Dynamic Arrays. So let those original fixed arrays be restricted as they wish, but Open Arrays and Dynamic Arrays are not Pascal citizens and are not obliged to be limited by its codebook!

Please, communicate Emba in QC or maybe even here, but if u just pass by without expressing your opinion - nothing would change!

3
No need to bring generics into this. Try type T1 = array of string; T2 = array of string and then attempt to assign a variable of type T1 to one of type T2.David Heffernan

3 Answers

11
votes

The key to understanding this issue is the Type Compatibility and Identity topic in the language guide. I suggest you have a good read of that topic.

It is also helpful to simplify the example. The inclusion of generics in the example serves mainly to complicate and confuse matters.

program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}

type
  TInteger1 = Integer;
  TInteger2 = Integer;
  TArray1 = array of Integer;
  TArray2 = array of Integer;
  TArray3 = TArray1;

var
  Integer1: TInteger1;
  Integer2: TInteger2;
  Array1: TArray1;
  Array2: TArray2;
  Array3: TArray3;

begin
  Integer1 := Integer2; // no error here
  Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
  Array1 := Array3; // no error here
end.

From the documentation:

When one type identifier is declared using another type identifier, without qualification, they denote the same type.

This means that TInteger1 and TInteger2 are the same type, and are indeed the same type as Integer.

A little further on in the documentation is this:

Language constructions that function as type names denote a different type each time they occur.

The declarations of TArray1 and TArray2 fall into this category. And that means that these two identifiers denote different types.

Now we need to look at the section discussing compatibility. This gives a set of rules to follow to determine whether or not two types are compatible or assignment compatible. We can in fact shortcut that discussion by referring to another help topic: Structured Types, Array Types and Assignments which states clearly:

Arrays are assignment-compatible only if they are of the same type.

This makes it clear why the assignment Array1 := Array2 results in a compiler error.

Your code looked at passing parameters, but mine focused on assignment. The issues are the same because, as the Calling Procedures and Functions help topic explains:

When calling a routine, remember that:

  • expressions used to pass typed const and value parameters must be assignment-compatible with the corresponding formal parameters.
  • .......
7
votes

Delphi is a strongly typed language. That means that identical (in this case I mean their definitions look exactly the same) types are not assignment compatible.

When you write array of <type> you are defining a type and not an alias. As David already said in his comment the two identical types like

type 
  T1 = array of string; 
  T2 = array of string;

are not assignment compatible.

Same goes for

type
  TStringDynArray = array of string;
  TArray<T> = array of string;

Often people forget about the incompatibility of identical types and my guess would be that they did when they introduced IOUtils for example. Theoretically the definition of TStringDynArray should have been changed to TStringDynArray = TArray<string> but I guess that could have raised other problems (not saying bugs with generics...).

2
votes

I also had the same problem with Delphi, where I wanted to pass values from one identical array to another. Not only did I have "incompatibility" problems with two like array assignments, but I also could not use the "Copy()" procedure. To get around this problem, I found that I could use a pointer to an type array of array of string, instead.

For example:

type RecArry = array of array of string
     end;
var TArryPtr : ^RecArry;

Now, I can pass the values from any fixed array to another identical array without any compatibility or function problems. For example:

TArryPtr := @RecArry.LstArray //This works!
TArryPtr := @LstArray         //This also works!

With this created array assignment template, I can now work with all two dimensional arrays without any problems. However, it should be understood, that when accessing this type of string array pointer, an extra element is created, so that when we would expect this type of array 2D array below, for example:

Two_Dimensional_Fixed_Array[10][0]

We now get an extra element adjusted array as seen here:

New_Two_Dimensional_Fixed_Array[10][1]    

This means that we have to use some slightly tricky code to access the pointer array, because all the populated elements in Two_Dimensional_Fixed_Array[10][0] have moved down, so that they are offset by 1, as in New_Two_Dimensional_Fixed_Array[10][1].

Therefore where we would normally find the value 'X' in Two_Dimensional_Fixed_Array[1][0], it will now be found here in TArryPtr[0][1].

Its a trade off we all have to live with!

Another important note to bear in mind is the definition of a pointer array when it is declared. When a pointer array is type declared, the Borland compiler will not allow the Pointer array to have the same element size as the array to which it is pointing too. For example, if an array is declared as:

Orig_Arry : array [1..50,1] of string;

The pointer array which should point to it would be declared in the following fashion:

Type Pntr_Arry : array [1..50,2] of string;

Did you notice the the extra element? I am guessing the the Borland compiler has to widen the array pointer to allow for the pointer address.