ceylon-dart
ceylon-dart copied to clipboard
perform 'late' value initialization non-polymorphically, *sometimes*
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?
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
.
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
}
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.
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.