-
Notifications
You must be signed in to change notification settings - Fork 151
Description
Description:
Adding @JsConstructor annotation with class inheritance changes constructor field initialization behavior. Default field initialization overwrites initialization by the programmer.
In the example code below, the superclass Shape
calls the non-final, non-private method initGraphic
. initGraphic
is overridden in Box
and sets the value of Box#drawLabel
to true
. (Calling non-final, non-private methods in a constructor is considered bad practice, but I'm working with a large amount of existing code written with this pattern.)
When the bug is triggered by having @JsConstructor
on Shape
's constructor, Shape
's J2CL generated constructor()
calls this.$ctor__com_example_Shape__void()
which calls Box
's initGraphic
. When Shape
's constructor()
returns to Box
's constructor()
, Box
's pre-initialization overwrites the initialization done in Box#initGraphic
.
To Reproduce:
J2CL-maven-plugin minimum-reproducible test project:
constructor-bug-test.zip
(I'm assuming this bug is in J2CL rather than j2clmavenplugin because it has to do with generated code, but I could be wrong.)
Example Code:
package com.example;
import elemental2.dom.DomGlobal;
import jsinterop.annotations.JsConstructor;
import jsinterop.annotations.JsType;
@JsType
public class Main {
public static void main(String[] args) {
Box box = new Box();
Main.print("5. Box final drawLabel=" + box.drawLabel);
}
public static void print(String s) {
if (DomGlobal.console == null) {
System.out.println(s);
} else {
DomGlobal.console.log(s);
}
}
}
abstract class Shape {
// This annotation triggers bug by changing the generated J2CL constructor initialization order.
// Commenting out this annotation will not trigger the bug.
@JsConstructor
public Shape() {
Main.print("1. ActiveBean ctor");
initGraphic();
}
protected void initGraphic() {
Main.print("2. ActiveBean initGraphic");
}
}
class Box extends Shape {
public boolean drawLabel;
@JsConstructor
public Box() {
super();
Main.print("4. Box ctor");
}
@Override
protected void initGraphic() {
super.initGraphic();
Main.print("3. Box initGraphic");
drawLabel = true;
}
}
Output behavior with @JsConstructor
annotation on `Shape (triggers bug):
1. ActiveBean ctor
2. ActiveBean initGraphic
3. Box initGraphic
4. Box ctor
5. Box final drawLabel=false <---- BUG!? Expected 'true'
Output behavior without @JsConstructor
annotation on Shape
(works as expected):
1. ActiveBean ctor
2. ActiveBean initGraphic
3. Box initGraphic
4. Box ctor
5. Box final drawLabel=true
Generated Code
With @JsConstructor on Shape (bug):
class Shape extends j_l_Object {
constructor() {
Shape.$clinit();
super();
this.$ctor__com_example_Shape__void(); // This call is not present when @JsConstructor is not present.
}
/** @nodts */
$ctor__com_example_Shape__void() {
this.$ctor__java_lang_Object__void();
Main.print('1. ActiveBean ctor');
this.m_initGraphic__void(); // Calls overridden child class method.
}
class Box extends Shape {
constructor() {
Box.$clinit();
super();
/**@type {boolean} @nodts*/
this.f_drawLabel__com_example_Box = false; // Pre-initialization overwrites the work done by the super-class constructor.
this.$ctor__com_example_Box__void();
}
/** @nodts */
$ctor__com_example_Box__void() {
Main.print('4. Box ctor');
}
Without @JsConstructor on Shape (no bug):
class Shape extends j_l_Object {
/** @protected @nodts */
constructor() {
super();
// Does not call $ctor__com_example_Shape__void()
}
/** @nodts */
$ctor__com_example_Shape__void() { // Not called until after Box's pre-initialization.
this.$ctor__java_lang_Object__void();
Main.print('1. ActiveBean ctor');
this.m_initGraphic__void();
}
class Box extends Shape {
constructor() {
Box.$clinit();
super();
/**@type {boolean} @nodts*/
this.f_drawLabel__com_example_Box = false; // Pre-initialization
this.$ctor__com_example_Box__void();
}
/** @nodts */
$ctor__com_example_Box__void() {
this.$ctor__com_example_Shape__void(); // Now Shape's java constructor is called after Box's pre-initialization.
Main.print('4. Box ctor');
}
Version:
J2clMavenPlugin: v20230718-1