diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index d8cc76973233..d3dad820dfc2 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -149,17 +149,21 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = // (2) If the parameter accessor reference was to an alias getter, // drop the () when replacing by the parameter. object intoConstr extends TreeMap { - private var isSuperCall = false + private var inSuperCall = false override def transform(tree: Tree)(using Context): Tree = tree match { case Ident(_) | Select(This(_), _) => var sym = tree.symbol - if sym.is(ParamAccessor) && (!sym.is(Mutable) || isSuperCall) - // Variables need to go through the getter since they might have been updated, - // except if we are in a super call, since then the virtual getter call would - // be illegal. - then + def isOverridableSelect = tree.isInstanceOf[Select] && !sym.isEffectivelyFinal + def keepUnlessInSuperCall = sym.is(Mutable) || isOverridableSelect + // If true, switch to constructor parameters only in the super call. + // Variables need to go through the getter since they might have been updated. + // References via this need to use the getter as well as long as that getter + // can be overriddem. This is needed to handle overrides correctly. See run/i15723.scala. + // But in a supercall we need to switch to parameters in any case since then + // calling the virtual getter would be illegal. + if sym.is(ParamAccessor) && (!keepUnlessInSuperCall || inSuperCall) then sym = sym.subst(accessors, paramSyms) - if (sym.maybeOwner.isConstructor) ref(sym).withSpan(tree.span) else tree + if sym.maybeOwner.isConstructor then ref(sym).withSpan(tree.span) else tree case Apply(fn, Nil) => val fn1 = transform(fn) if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor) @@ -170,7 +174,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = } def apply(tree: Tree, prevOwner: Symbol)(using Context): Tree = - isSuperCall = isSuperConstrCall(tree) + inSuperCall = isSuperConstrCall(tree) transform(tree).changeOwnerAfter(prevOwner, constr.symbol, thisPhase) } diff --git a/tests/run/i15723.check b/tests/run/i15723.check new file mode 100644 index 000000000000..b611ed20cf6c --- /dev/null +++ b/tests/run/i15723.check @@ -0,0 +1,2 @@ +20 +20 diff --git a/tests/run/i15723.scala b/tests/run/i15723.scala new file mode 100644 index 000000000000..2169857eace9 --- /dev/null +++ b/tests/run/i15723.scala @@ -0,0 +1,10 @@ +class B(val y: Int) { + println(this.y) + foo() + def foo() = println(this.y) +} +class C(override val y: Int) extends B(10) + +object Test extends App { + new C(20) +} \ No newline at end of file