I was just looking at this
http://d.puremagic.com/issues/show_bug.cgi?id=2544
which describes how it's possible to bypass const by doing this:

    const(int)[] answers = [42];
    int[][] unconsted = [[]];
    const(int)[][] unsafe = unconsted;
    unsafe[0] = answers;
    unconsted[0][0] = 43;

The problem is that converting from int[][] to const(int)[][] isn't safe, even though the language/compiler seems to think it is.

Really, it's another version of how you can use a DerivedClass[] as a BaseClass[] and thereby place in it an object that isn't of type DerivedClass.

There's actually a simple solution to this: specify that, where DerivedClass derives from BaseClass, DerivedClass[] cannot be implicitly converted to BaseClass[], but only to const(BaseClass)[].

Java has had something like this for a while, albeit not with arrays. That is, IIRC, you can assign a DataStructure<DerivedClass> to a variable of type DataStructure<? extends BaseClass> (or even DataStructure<? extends DerivedClass>) - this creates a read-only view of the data structure. My proposal implements the same basic concept as this, but in a simpler way. (Java also supports write-only 'views' with DataStructure<? super FurtherDerivedClass>, but I'm not sure we need anything like this in D at the moment.)

Now let's apply the same principle to the example in the bug report. Try defining that, in general, T[][] can be converted to const(T[])[] but not const(T)[][]. Then

    const(int)[] answers = [42];
    int[][] unconsted = [[]];
    const(int)[][] unsafe = unconsted;

would be illegal.  One would have to do

    const(int[])[] safe = unconsted;

and now

    safe[0] = answers;

is illegal.

In order to deal with the original, slightly more complex testcase, the principle needs to be applied in the same way to further levels of array nesting. It would need applying to pointer types as well as array types - so, for example,

    int[]*[]

would be implicitly convertible to

    const(int[]*)[]

but not

    const(int[])*[]
    const(int)[]*[]

We could combine two applications of the principle, and get

    DerivedClass[][]

convertible to

    const(DerivedClass[])[]
    const(BaseClass[])[]

but not

    const(DerivedClass)[][]
    const(BaseClass)[][]
    BaseClass[][]


To summarise, the rules would be:

- Generalise the definition of an upcast to be any of the following:
-- conversion of a class type to a class type further up the hierarchy
-- conversion of a type to a const version of that type
-- a legal implicit conversion according to the following rule

- If U is an upcast of T, then a legal implicit conversion is from T[] to const(U)[], or T* to const(U)*. In particular, conversion from T[] to U[] or T* to U* is illegal, except in the cases where T and const(T) are exactly the same.


If I've worked it out right, then these rules'll be fix the const system to be safe, while at the same time making upcasting of object arrays safe. At least, before you consider invariant - a little more thought is needed to work out how this would be dealt with.

And it shouldn't break too much existing code. Code that is already broken will just promote this breakage from runtime to compiletime, and those uses that were already 'correct' will be easily fixed by updating the declarations. The unsafe conversions could be deprecated before being removed altogether, with messages such as

conversion from int[][] to const(int)[][] is unsafe and deprecated, use const(int[])[] instead conversion from DerivedClass[] to BaseClass[] is unsafe and deprecated, use const(BaseClass)[] instead

These unsafe conversions could still be allowed by explicit casts, should they be needed for something, IWC you'd be expected to know what you're doing.

What does everyone think? Even better, can anyone find any holes that my proposal misses?

Stewart.

Reply via email to