-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Locals stored as private symbols on non-native JS types breaks JS semantics #4968
Comments
This seems to workaround the problem:
|
AFAICT, this is as designed. The If you want absolute control over the internals of the produced object, you can create as a |
My 2 cts here: I do agree that the current behavior violates the principle of least surprise. In fact, I do not understand why we need the private field: object HelloWorld {
trait Config extends js.Object {
def title(): String
}
def config() = {
// Inlining this fixes the problem.
// Moving it out of the current block scope also fixes the problem.
// It is getting stored as a private symbol on the config object.
val routeTitle = "My page"
val config = new Config {
override def title(): String = routeTitle
}
config
}
} Compiles to: module class helloworld.HelloWorld$ extends java.lang.Object {
def config;Lhelloworld.HelloWorld$Config(): any = {
val routeTitle: java.lang.String = "My page";
val config: any = (arrow-lambda<superClass$: any = constructorOf[scala.scalajs.js.Object], routeTitle$1$2{routeTitle$1}: java.lang.String = routeTitle>(): any = {
var routeTitle$1: java.lang.String = null;
val overload: int = {
routeTitle$1 = routeTitle$1$2;
0
};
val this{this}: any = {};
global:Object["defineProperty"](this, mod:scala.scalajs.runtime.package$.privateFieldsSymbol;Ljava.lang.Object(), {
"value": {
"helloworld.HelloWorld$$anon$1::routeTitle$1": null
}
});
this["title"] = (lambda<>(): any = {
helloworld.HelloWorld$$anon$1::title;Lhelloworld.HelloWorld$$anon$1;Ljava.lang.String(this)
});
this[mod:scala.scalajs.runtime.package$.privateFieldsSymbol;Ljava.lang.Object()]["routeTitle$1"] = routeTitle$1;
(void 0);
this
})();
config
}
constructor def <init>;V() {
this.java.lang.Object::<init>;V();
<storeModule>
}
} But the following would be clearly better: module class helloworld.HelloWorld$ extends java.lang.Object {
def config;Lhelloworld.HelloWorld$Config(): any = {
val routeTitle: java.lang.String = "My page";
val config: any = (arrow-lambda<superClass$: any = constructorOf[scala.scalajs.js.Object], routeTitle$1$2{routeTitle$1}: java.lang.String = routeTitle>(): any = {
var routeTitle$1: java.lang.String = null;
val overload: int = {
routeTitle$1 = routeTitle$1$2;
0
};
val this{this}: any = {};
this["title"] = (lambda<routeTitle$3 = routeTitle$1>(): any = {
routeTitle$3
});
(void 0);
this
})();
config
}
constructor def <init>;V() {
this.java.lang.Object::<init>;V();
<storeModule>
}
} I realize that this will need much more massaging the Scala compiler. But is there ever a case where a capture in Scala cannot be compiled to a closure or class capture in Scala.js IR? |
Scastie: https://scastie.scala-lang.org/thK7wor1S7OeKO9smIZx9Q
Repo: https://github.com/steinybot/bug-reports/tree/scala-js/combine-objects-private-fields
Attempting to run:
fails with:
The issue seems to be the way that
routeTitle
is being stored on theconfig
object within thePrivateFieldsSymbolHolder
property which is non-enumerable. When the values fromconfig
are copied over to the new object this non-enumerable property is lost. Then when we invoke thetitle
function the implementation attempts to get thePrivateFieldsSymbolHolder
but it is undefined.You could argue that this is not breaking JS semantics because non-enumerable properties are never copied. You could run into the same thing with JS:
The issue is that as a programmer we did not choose to use a non-enumerable property to store the local value, that is implementation specific. It seems like a particularly difficult foot gun to track down (took me a few hours).
In this particular case, using
PrivateFieldsSymbolHolder
seems to be redundant. A closure would capture the value just fine.Is there a reliable way to workaround this and prevent it from being stored on the
PrivateFieldsSymbolHolder
?The text was updated successfully, but these errors were encountered: