16

Today I discovered a compiler bug (QC#108577).

The following program fails to compile:

program Project1;
{$APPTYPE CONSOLE}

procedure P(M: TArray<TArray<Integer>>);
begin
  SetLength(M, 1, 2);
end;

begin
end.

The compiler gags on the SetLength line and says:

[dcc32 Error] E2029 ')' expected but ',' found

I know I could fix it like this:

procedure P(M: TArray<TArray<Integer>>);
var
  i: Integer;
begin
  SetLength(M, 1);
  for i := low(M) to high(M) do
    SetLength(M[i], 2);
end;

but naturally I'm keen to avoid having to resort to this.

The following variant compiles and seems to work:

procedure P(M: TArray<TArray<Integer>>);
type
  TArrayOfArrayOfInteger = array of array of Integer;
begin
  SetLength(TArrayOfArrayOfInteger(M), 1, 2);
end;

I don't know enough about the implementation details of dynamic arrays, TArray<T> casting, reference counting etc. to be confident that this is safe.

Is there anybody out there who does know enough to say one way or another whether or not this will produce the correct code at runtime?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    `System.pas` defines `TArray = array of T;` so I'd expect that a hard cast *should* work. – afrazier Sep 07 '12 at 16:12
  • 1
    Either one ends up in 'DynArraySetLength' (at least one with a 1 dimensional array anyway), so I'd agree the above.. – Sertac Akyuz Sep 07 '12 at 16:32
  • @afrazier System.pas is special though and there could be some special compiler magic intrinsic treatment for `TArray`. That's what I'm concerned about. Naturally it's pretty unlikely that they would implement a new incompatible array code rather than use the tried and tested one that they already have. – David Heffernan Sep 07 '12 at 16:46
  • @SertacAkyuz That's very strong evidence. Could you expand on it as an answer please? – David Heffernan Sep 07 '12 at 16:49
  • I thought that System was only special in that the predefined bits and compiler magic were automatigcally linked into it by the compiler? Doesn't nearly all the code in `System.pas` still have to be proper compilable Delphi code? – afrazier Sep 07 '12 at 16:58
  • @afrazier Compilability implies nothing about the implementation and any code gen. – David Heffernan Sep 07 '12 at 17:10
  • Funny, David. Looks like you was also bitten by that http://stackoverflow.com/questions/11029353 Would you vote for my QC ? :-) – Arioch 'The Sep 07 '12 at 17:39
  • @Arioch'The I'd vote for a feature request that `TStringDynArray` was replaced with `TArray`. And likewise for `TDoubleDynArray` and so on. Much much easier to change RTL than language. Also, why don't you use your real name? It would be much better if it was obvious that Arioch'The from SO was the same as Dmitry Burov from Emba forums! – David Heffernan Sep 07 '12 at 20:00
  • @David That is traditionally, more than decade. And by the way, once i went to the hospital just to get my own death notice. In my street in next house there lived one more Burov Dmitry. So, "Arioch" moniker is much more unique, at least in Russia, hence is better ID. ------------ Replaced where ? what point in that replacement ? If you basicalyl cannot assign TArray type variables - that makes functional style very limited. You would use two libraries using those types under different local names - and You're back to square one - manual explicit typecasting. – Arioch 'The Sep 09 '12 at 16:07
  • RTL would never cover all practically useful aray types. And instead of declaring dozens of array aliases you can do one singl change in the language - made htose arrays "assignment-cmpatible" like for example sets and different shortstrings already are. – Arioch 'The Sep 09 '12 at 16:10
  • @Arioch'The No, all RTL can just change to `TArray` and then there's no need to change language or compiler. – David Heffernan Sep 09 '12 at 16:14
  • @David what about 3rd party libs, that can not afford supporting the only most recent version of compiler ? – Arioch 'The Sep 11 '12 at 05:43
  • @Arioch That reasoning just holds Delphi back. – David Heffernan Sep 11 '12 at 06:05
  • @David damaging 3rd party ecosystem would help Delphi much, yeah. Making Delphi language consists of "special cases" C++-like would help too. After you make that RTL switch much of users' codebase would have to be redone, since their procedure xxx(vvv:array of ...) would work no more. That is reasonless compiler limitations that are holding it back and forcing into ugly workarounds, that can only fix one issue by introducing another. – Arioch 'The Sep 11 '12 at 06:30
  • @Arioch procedure xxx(vvv:array of ...) is an open array. 3rd party ecosystem survived Unicode. This is trivial in contrast. Many 3rd party component vendors are ditching legacy Delphi. They don't want to be held back. – David Heffernan Sep 11 '12 at 06:34
  • @David yes, it is open array, so what ? putting labels does not explain why this valid code should suddenly compile no more and why that is good. Unicode is kind of scare story of big bad wolf. But until you go low-level with DLLs, pointers, direct char-to-byte typecasts - that just does not matter. The same sources are compiled with both Unicode and non-Unicode versions. What you suggest - is sudden change of API, so that old RTL calls would work no more. Both in libraries and in long-running projects. – Arioch 'The Sep 11 '12 at 06:42
  • @Arioch Open arrays can receive parameters of type TArray. The reason for using TArray throughtout RTL is that it makes generic classes compatible with RTL. See my comment to Arnaud's answer. – David Heffernan Sep 11 '12 at 06:47
  • @David you can still connect those parts of RTL using explicit typecast. Frankly, it is not obvious that EMB should break a lot of EXISTING code just to save few lines for you to create NEW code. You basically say "let all old code adapt to my preferred ways or die". But why can't we have both old code and new code live together? – Arioch 'The Sep 11 '12 at 06:57
  • @Arioch Well, just like you, I want to avoid explicit typecast. I also doubt any change will be made, either to lang or RTL. – David Heffernan Sep 11 '12 at 07:02
  • @David maybe XE3 extended record helpers can enable implicit typecasts ? – Arioch 'The Sep 11 '12 at 07:25
  • @Arioch'The You can't have helpers for generic types. Only specific types. I think! – David Heffernan Sep 11 '12 at 08:01
  • I verified the bug is also present in Delphi XE, so you can update the QC entry. – Jeroen Wiert Pluimers Sep 20 '12 at 13:45
  • @JeroenWiertPluimers The QC report reads 2010, ... XE3 which I take to mean all versions between 2010 and XE3. – David Heffernan Sep 20 '12 at 13:49
  • I missed that part. Since the QC report also says "The fault is present in D2010, XE2, XE3." I verified it in XE (; – Jeroen Wiert Pluimers Sep 20 '12 at 15:57
  • 1
    The bug was fixed in XE4. – LU RD Feb 19 '14 at 23:07

3 Answers3

18

The compiler intrinsic procedure SetLength constructs an array of dimensions on the fly on the stack and calls DynArraySetLength for any dynamic array, be it generic or not. If a generic array wouldn't be structurally compatible with a regular dynamic array, the same implementation for setting the length possibly wouldn't be called.

In fact documentation of DynArraySetLength offers SetLength as an alternative for multi-dimensional arrays. DynArraySetLength could also be used instead of a typecast, but I don't see any reason to prefer one or the other.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
3

By design of the generics implementation, using a manual map to array of array of Integer will work.

But there is no benefit of using generics here!

Just code:

type
  TArrayOfArrayOfInteger = array of array of Integer;

procedure P(M: TArrayOfArrayOfInteger);
begin
  SetLength(TArrayOfArrayOfInteger, 1, 2);
end;

Note also that such TArray<> or array of .. are passed by value, and copied on the stack, unless you specify const or var:

procedure P(var M: TArrayOfArrayOfInteger);
begin
  SetLength(TArrayOfArrayOfInteger, 1, 2);
end; // now caller instance of the parameter will be resized

var A: TArrayOfArrayOfInteger;
...
A := nil;
P(A);
assert(length(A)=1);
assert(length(A[0])=2);
Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • Do you have any documents to back up the "by design" assertion? There is a benefit in using generics here. In a generic container class I can write a function like this: `function ToArray: TArray`. Clearly I can't do that without using the generic `TArray`. And I know all about var and const. My example is clearly useless and pointless. It was designed to be the smallest possible example that illustrated the bug. Given that the code cannot run, worrying about runtime performance seems pointless. – David Heffernan Sep 07 '12 at 16:43
  • @David using generics you can declare a function like *function ToArray: array of T* and using nested types would even allow you to name it. – Arioch 'The Sep 11 '12 at 06:51
  • @Arioch No, that code doesn't compile. There's no need to name TArray. It already has a name. One name is enough. – David Heffernan Sep 11 '12 at 06:58
  • @David, Yes, it already has a name and name is "array of T" ;-) I fail to see why you protect segregation of two ways to declare an array. Especially for that segregation is only backed up by 70-s "Pascal Report" that was already subverted for most other types like sets and ranges. – Arioch 'The Sep 11 '12 at 07:10
  • @Arioch That's your name for it. Using that name means your code won't compile. Pretty useless idea. The change you propose is sensible but I think hell will freeze over before Emba implement it. I may be wrong but I judge that a small number of RTL changes are more likely. – David Heffernan Sep 11 '12 at 07:16
  • @David why your code does not compile ? I just tried in XE2 - it DOES compile! http://pastebin.ca/2203115 – Arioch 'The Sep 11 '12 at 07:23
  • They still need to change something, @David. By fixing my issue they would also fix yours and Remy's ones, as a free bonus. My proposal is unification and reducing code and specialcasing in both compiler-RTTI-RTL and in users code. You proposal is introducing yet another special case. And i suspect that if they'd fix it ad hoc, then QC would be opened for TARRAY>> – Arioch 'The Sep 11 '12 at 07:34
  • What doesn't compile is this, from your comment above: function ToArray: array of T. Your TA from your pastebin is not needed. Using TArray is clearly simpler. No special case in my proposal. – David Heffernan Sep 11 '12 at 13:28
2

I was recently bitten by the fact that DynamicArray<T> and TArray<T> in C++ are actually implemented differently (DynamicArray is a standalone class, whereas TArray is a TObject descendant), which implies that array of T and TArray<T> do have some implementation differences in Delphi as well. They certainly produce different types of RTTI, at least. Which was the root cause of a problem in some of my C++ code that started failing when the Delphi compiler started outputting TArray typedefs in HPP files for Delphi array of ... types instead of DynamicArray typedefs.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I don't know Emba's C++ at all. What are `DynamicArray` and `TArray` in Emba C++ and how do they relate to Delphi? – David Heffernan Sep 08 '12 at 07:50
  • 1
    They are templated classes that directly correspond to Delphi's `array of ...` and `TArray` types, much like the `AnsiString`, `UnicodeString` and `Variant` classes directly correspond to their Delphi counterparts. They are all C++ classes that allow C++ and Delphi to exchange compatible data with each other. – Remy Lebeau Sep 08 '12 at 19:40
  • Then it yet another case at EMB when "left hand is oblivious to right hand's doings". In delphi *type TArray = array of T*, by definition. It is not class like TList. C++ is just different from Delphi once again. – Arioch 'The Sep 11 '12 at 07:31
  • 2
    But `TArray` is a Generic, so I'm sure that also plays into the issues as well. I'm guessing that `TArray` and `array of Byte` are actually different because of that. They does explain why they have different RTTI. – Remy Lebeau Sep 11 '12 at 21:12