Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upMixed-in field in class inside lazy val rhs is erroneously immutable in the IR -> IR checking error #3918
Comments
|
I discovered this while trying to upgrade https://github.com/lihaoyi/sourcecode to Scala.js 1.0.0-RC2. There are two tests ( I do not think this is a blocker for 1.0.0.
|
|
An ideal fix would be to fix scalac upstream not to create two different symbols. A workaround in Scala.js would be to use the |
WorkaroundAs a workaround, put the rhs of the For example, for the reproduction above, replace with: class Apply {
def testTraitMixinInLocalLazyVal(): Unit = {
trait TraitMixedInLocalLazyVal {
val foo = "foobar"
}
def localLazyValWorkaroundScalaJSIssue3918 = {
class ClassExtendsTraitInLocalLazyVal extends TraitMixedInLocalLazyVal
val obj = new ClassExtendsTraitInLocalLazyVal
assert(obj.foo == "foobar")
}
lazy val localLazyVal = localLazyValWorkaroundScalaJSIssue3918
localLazyVal
}
} |
|
Is this reported as a bug upstream already? |
|
No, not yet. |
|
/cc @lrytz |
|
This is [[syntax trees at end of mixin]] // HelloWorld.scala
package helloworld#14 {
class Apply#5004 extends Object#419 {
def testTraitMixinInLocalLazyVal#5011(): Unit#2093 = {
lazy <artifact> val localLazyVal$lzy#9299: scala#7.runtime#2160.LazyUnit#2763 = new scala#7.runtime#2160.LazyUnit#2763();
Apply#5004.this.localLazyVal$1#6485(localLazyVal$lzy#9299)
};
final <artifact> private[this] def localLazyVal$lzycompute$1#9306(localLazyVal$lzy$1#12994: scala#7.runtime#2160.LazyUnit#2763): Unit#2093 = localLazyVal$lzy$1#12994.synchronized#4047[Unit#2093](if (localLazyVal$lzy$1#12994.initialized#9302())
()
else
{
{
val obj#6490: helloworld#15.Apply$ClassExtendsTraitInLocalLazyVal$1#6489 = new helloworld#15.Apply$ClassExtendsTraitInLocalLazyVal$1#6489(Apply#5004.this);
scala#6.Predef#1566.assert#4631(obj#6490.foo#6487().==#4042("foobar"))
};
localLazyVal$lzy$1#12994.initialize#9303()
});
final lazy private[this] def localLazyVal$1#6485(localLazyVal$lzy$1#12995: scala#7.runtime#2160.LazyUnit#2763): Unit#2093 = if (localLazyVal$lzy$1#12995.initialized#9302())
()
else
Apply#5004.this.localLazyVal$lzycompute$1#9306(localLazyVal$lzy$1#12995);
def <init>#5010(): helloworld#15.Apply#5004 = {
Apply#5004.super.<init>#892();
()
}
};
object HelloWorld#5006 extends Object#419 {
def main#6527(args#6529: Array#1076[String#698]): Unit#2093 = new helloworld#15.Apply#5004().testTraitMixinInLocalLazyVal#5011();
def <init>#6526(): helloworld#15.HelloWorld#5006.type = {
HelloWorld#5006.super.<init>#892();
()
}
};
abstract trait Apply$TraitMixedInLocalLazyVal$1#6484 extends Object#419 {
<accessor> <sub_synth> protected[this] def helloworld$Apply$TraitMixedInLocalLazyVal$_setter_$foo_=#9292(x$1#9293: String#698): Unit#2093;
<stable> <accessor> <sub_synth> def foo#6487(): String#698;
<synthetic> <stable> <artifact> def $outer#12955(): helloworld#15.Apply#5004;
def /*Apply$TraitMixedInLocalLazyVal$1*/$init$#6486(): Unit#2093 = {
Apply$TraitMixedInLocalLazyVal$1#6484.this.helloworld$Apply$TraitMixedInLocalLazyVal$_setter_$foo_=#9292(("foobar": String#698));
()
}
};
class Apply$ClassExtendsTraitInLocalLazyVal$1#6489 extends Object#419 with helloworld#15.Apply$TraitMixedInLocalLazyVal$1#6484 {
override <stable> <accessor> def foo#9295(): String#698 = (Apply$ClassExtendsTraitInLocalLazyVal$1#6489.this.foo#9294: String#698);
private[this] val foo#9294: String#698 = _;
override <accessor> protected[this] def helloworld$Apply$TraitMixedInLocalLazyVal$_setter_$foo_=#9296(x$1#9298: String#698): Unit#2093 = Apply$ClassExtendsTraitInLocalLazyVal$1#6489.this.foo#9294 = (x$1#9298: String#698);
<synthetic> <paramaccessor> <artifact> private[this] val $outer#12957: helloworld#15.Apply#5004 = _;
<synthetic> <stable> <artifact> def $outer#12958(): helloworld#15.Apply#5004 = Apply$ClassExtendsTraitInLocalLazyVal$1#6489.this.$outer#12957;
def <init>#6491($outer#12959: helloworld#15.Apply#5004): helloworld#15.Apply$ClassExtendsTraitInLocalLazyVal$1#6489 = {
if ($outer#12959.eq#4038(null))
throw null
else
Apply$ClassExtendsTraitInLocalLazyVal$1#6489.this.$outer#12957 = $outer#12959;
Apply$ClassExtendsTraitInLocalLazyVal$1#6489.super.<init>#892();
Apply$ClassExtendsTraitInLocalLazyVal$1#6489.super./*Apply$TraitMixedInLocalLazyVal$1*/$init$#6486();
()
}
}
} |
|
The relevant parts are (field def and field setter). private[this] val foo#9294: String#698 = _;
override <accessor> protected[this] def helloworld$Apply$TraitMixedInLocalLazyVal$_setter_$foo_=#9296(x$1#9298: String#698): Unit#2093 =
Apply$ClassExtendsTraitInLocalLazyVal$1#6489.this.foo#9294 = (x$1#9298: String#698); |
|
printing symbol and ID when we assign / define yields this: assign: value foo #9294 We conclude that the symbol in |
|
So it seems the tree looks correct (as posted above by @gzm0). But looking at the symbol of
So there's a variable |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

Reproduction
Reproducible in 0.6.x and 1.x, in 2.12 and 2.13. In 2.11, things work correctly. Full repro:
In 2.12 and 2.13, trying to link the above program will cause the following errors:
Workaround
A workaround is given down below at #3918 (comment).
Analysis
The IR of the inner class is:
in which the field
foois immutable. This is incorrect because it is assigned to in the trait setterdef helloworld$Apply$TraitMixedInLocalLazyVal$_setter_$foo_$eq;Ljava.lang.String;V. One problem is that, for scalac, that field is not marked asMUTABLE. This has always been an issue forvalfields in traits, so we have logic that patches up fields that are "unexpectedly" mutated. They are recorded as such incase Assign(...) =>atscala-js/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
Lines 2359 to 2360 in 043912f
and patched up in
genClassFieldsatscala-js/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
Lines 1130 to 1133 in 043912f
However, when all the following conditions are met:
lazy vallazy valis inside adef(so it's a local lazy val, not a field)then scalac ends up creating 2 different
Symbols for that field. One is used in theAssignnode, and the other is listed a member of the class, and hence used bygenClassFields.This causes our patching up to fail to process that field, since it is not the same symbol (but they have the same name/owner/etc.).