ceylon-dart icon indicating copy to clipboard operation
ceylon-dart copied to clipboard

perform 'late' value initialization non-polymorphically, *sometimes*

Open jvasileff opened this issue 8 years ago • 4 comments

The most reasonable (edit: or not... see below) output for the code below is what the JVM produces, where the assignment to a late value is non-polymorphic unless the value has a late or variable refinement.

I'm guessing the JVM behavior is a natural result of setX() refinements existing for j and k, but not i.

class A() {
    shared default late Integer i;
    shared default late Integer j;
    shared default late Integer k;

    shared void initialize(Integer x) {
        i = x;
        j = x;
        k = x;
    }
}

class B() extends A() {
    shared actual Integer i = 1;
    shared actual variable Integer j = 1;
    shared actual late Integer k;
}

shared void runit() {
    value b = B();
    b.initialize(2);
    printAll { b.i, b.j, b.k };
    // {1, 2, 2}      on JVM
    // {1, 1, Crash!} on JS
    // {2, 2, 2}      on Dart
}

OTOH, @gavinking, should each of these be allowed?

jvasileff avatar Apr 01 '16 04:04 jvasileff

Of course, when they are initialized polymorphically, there is no way to initialize the refined value. So, within B, super.j and super.k will always throw an InitializationError.

jvasileff avatar Apr 01 '16 05:04 jvasileff

Actually, the results on the JVM are inconsistent too, when the late field is covariantly refined!

class AA() {
    shared default late Object i;
    shared default late Object j;

    shared void initialize(Object x) {
        this.i = x;
        this.j = x;
    }
}

class BB() extends AA() {
    shared actual variable Object i = "1";
    shared actual variable String j = "1"; 
}

shared void runX() {
    value b1 = BB();
    b1.initialize(2);
    printAll { b1.i, b1.j };
    // 2, 1 on JVM
    // 1, 1 on JS
    // 2, 2 on Dart
}

And initializing refined late attributes is impossible on the JVM:

class AAA() {
    shared default late Object i;

    shared default void initialize(Object x) {
        this.i = x;
    }
}

class BBB() extends AAA() {
    shared actual late Object i;

    shared actual void initialize(Object x) {
        super.initialize(1);
        this.i = x;
        // JVM ERROR: ceylon.language.InitializationError "Re-initialization of 'late' attribute"
    }

    shared Object superI => super.i;
}

shared void run() {
    value b1 = BBB();
    b1.initialize(2);
    print(b1.i); // 2
    print(b1.superI);
    // JVM: ERROR: ceylon.language.InitializationError "Accessing uninitialized 'late' attribute 'i'"
    // JS: 1
}

jvasileff avatar Apr 02 '16 14:04 jvasileff

A couple options:

Option A

  • late, late variable: non-default, so no problem.
  • default late: disallow.
  • default late variable: polymorphic assignment. How do we initialized the refined value? Do we care?

Option B

Same as A), but allow default late with non-polymorphic assignments. If refined by a late or a variable late, the refinement must always handle its own initialization.

I'm currently leaning towards B), which pretty much matches the current JS behavior.

jvasileff avatar Apr 02 '16 14:04 jvasileff

To be clear, we know that

shared default Integer x = 0;

"non-polymorphically" initializes x. It does not assign 0 to a potential refinement of x. super.x from within a subclass will be 0, while someInstance.x may not be 0 if x is refined.

It seems natural that the following would be exactly the same:

shared default late Integer x;
x = 0;

But, when x is variable:

shared default late variable Integer x;
x = 0;

x = 0 is ambiguous. Is it meant to initialize x, as in the previous examples? Or is it a normal assignment to the variable x, which, at least for non-late values, is polymorphic? The latter seems to be the reasonable choice. By annotating with variable, you forgo the ability to perform normal initialization on the value.

jvasileff avatar Apr 02 '16 15:04 jvasileff