Skip to content

Commit 3ff7d72

Browse files
authored
Merge pull request scala-js#4535 from sjrd/fix-mixed-in-field-mutable-bug
Fix scala-js#3918: Record names of mutated fields instead of symbols.
2 parents 1874820 + c6797d0 commit 3ff7d72

File tree

2 files changed

+48
-25
lines changed

2 files changed

+48
-25
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
148148
// Scoped state ------------------------------------------------------------
149149
// Per class body
150150
val currentClassSym = new ScopedVar[Symbol]
151-
private val unexpectedMutatedFields = new ScopedVar[mutable.Set[Symbol]]
151+
private val fieldsMutatedInCurrentClass = new ScopedVar[mutable.Set[Name]]
152152
private val generatedSAMWrapperCount = new ScopedVar[VarBox[Int]]
153153

154154
private def currentClassType = encodeClassType(currentClassSym)
@@ -213,7 +213,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
213213
private def nestedGenerateClass[T](clsSym: Symbol)(body: => T): T = {
214214
withScopedVars(
215215
currentClassSym := clsSym,
216-
unexpectedMutatedFields := mutable.Set.empty,
216+
fieldsMutatedInCurrentClass := mutable.Set.empty,
217217
generatedSAMWrapperCount := new VarBox(0),
218218
currentMethodSym := null,
219219
thisLocalVarIdent := null,
@@ -339,8 +339,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
339339

340340
if (!isPrimitive && !isJSImplClass(sym)) {
341341
withScopedVars(
342-
currentClassSym := sym,
343-
unexpectedMutatedFields := mutable.Set.empty,
342+
currentClassSym := sym,
343+
fieldsMutatedInCurrentClass := mutable.Set.empty,
344344
generatedSAMWrapperCount := new VarBox(0)
345345
) {
346346
try {
@@ -1255,7 +1255,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
12551255

12561256
val mutable = {
12571257
static || // static fields must always be mutable
1258-
suspectFieldMutable(f) || unexpectedMutatedFields.contains(f)
1258+
f.isMutable || // mutable fields can be mutated from anywhere
1259+
fieldsMutatedInCurrentClass.contains(f.name) // the field is mutated in the current class
12591260
}
12601261

12611262
val namespace =
@@ -2466,13 +2467,34 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
24662467
def genRhs = genExpr(rhs)
24672468
lhs match {
24682469
case Select(qualifier, _) =>
2469-
val ctorAssignment = (
2470-
currentMethodSym.isClassConstructor &&
2471-
currentMethodSym.owner == qualifier.symbol &&
2472-
qualifier.isInstanceOf[This]
2473-
)
2474-
if (!ctorAssignment && !suspectFieldMutable(sym))
2475-
unexpectedMutatedFields += sym
2470+
/* Record assignments to fields of the current class.
2471+
*
2472+
* We only do that for fields of the current class sym. For other
2473+
* fields, even if we recorded it, we would forget them when
2474+
* `fieldsMutatedInCurrentClass` is reset when going out of the
2475+
* class. If we assign to an immutable field in a different
2476+
* class, it will be reported as an IR checking error.
2477+
*
2478+
* Assignments to `this.` fields in the constructor are valid
2479+
* even for immutable fields, and are therefore not recorded.
2480+
*
2481+
* #3918 We record the *names* of the fields instead of their
2482+
* symbols because, in rare cases, scalac has different fields in
2483+
* the trees than in the class' decls. Since we only record fields
2484+
* from the current class, names are non ambiguous. For the same
2485+
* reason, we record assignments to *all* fields, not only the
2486+
* immutable ones, because in 2.13 the symbol here can be mutable
2487+
* but not the one in the class' decls.
2488+
*/
2489+
if (sym.owner == currentClassSym.get) {
2490+
val ctorAssignment = (
2491+
currentMethodSym.isClassConstructor &&
2492+
currentMethodSym.owner == qualifier.symbol &&
2493+
qualifier.isInstanceOf[This]
2494+
)
2495+
if (!ctorAssignment)
2496+
fieldsMutatedInCurrentClass += sym.name
2497+
}
24762498

24772499
def genBoxedRhs: js.Tree = {
24782500
val tpeEnteringPosterasure =
@@ -6688,19 +6710,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
66886710
else result.alternatives.head
66896711
}
66906712

6691-
/** Whether a field is suspected to be mutable in the IR's terms
6692-
*
6693-
* A field is mutable in the IR, if it is assigned to elsewhere than in the
6694-
* constructor of its class.
6695-
*
6696-
* Mixed-in fields are always mutable, since they will be assigned to in
6697-
* a trait initializer (rather than a constructor).
6698-
*/
6699-
private def suspectFieldMutable(sym: Symbol) = {
6700-
import scala.reflect.internal.Flags
6701-
sym.hasFlag(Flags.MIXEDIN) || sym.isMutable
6702-
}
6703-
67046713
private def isStringType(tpe: Type): Boolean =
67056714
tpe.typeSymbol == StringClass
67066715

test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,20 @@ class RegressionTest {
891891
assertTrue(result)
892892
}
893893

894+
@Test
895+
def traitMixinInLocalLazyVal_Issue3918(): Unit = {
896+
trait TraitMixedInLocalLazyVal {
897+
val foo = "foobar"
898+
}
899+
900+
lazy val localLazyVal = {
901+
class ClassExtendsTraitInLocalLazyVal extends TraitMixedInLocalLazyVal
902+
val obj = new ClassExtendsTraitInLocalLazyVal
903+
obj.foo
904+
}
905+
assertEquals("foobar", localLazyVal)
906+
}
907+
894908
}
895909

896910
object RegressionTest {

0 commit comments

Comments
 (0)