Skip to content

Commit 5cb511a

Browse files
authored
Merge pull request #8433 from dotty-staging/fix-#8425
Fix #8425: Avoid race between erasure and explicit outer
2 parents fa23429 + 99d8be8 commit 5cb511a

File tree

5 files changed

+47
-15
lines changed

5 files changed

+47
-15
lines changed

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ object Erasure {
710710
if (sym.isEffectivelyErased) erasedDef(sym)
711711
else
712712
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
713-
var vparams = outer.paramDefs(sym)
713+
var vparams = outerParamDefs(sym)
714714
::: ddef.vparamss.flatten.filterConserve(!_.symbol.is(Flags.Erased))
715715

716716
def skipContextClosures(rhs: Tree, crCount: Int)(using Context): Tree =
@@ -746,6 +746,28 @@ object Erasure {
746746
super.typedDefDef(ddef1, sym)
747747
end typedDefDef
748748

749+
/** The outer parameter definition of a constructor if it needs one */
750+
private def outerParamDefs(constr: Symbol)(using ctx: Context): List[ValDef] =
751+
if constr.isConstructor && hasOuterParam(constr.owner.asClass) then
752+
constr.info match
753+
case MethodTpe(outerName :: _, outerType :: _, _) =>
754+
val outerSym = ctx.newSymbol(constr, outerName, Flags.Param, outerType)
755+
ValDef(outerSym) :: Nil
756+
case _ =>
757+
// There's a possible race condition that a constructor was looked at
758+
// after erasure before we had a chance to run ExplicitOuter on its class
759+
// If furthermore the enclosing class does not always have constructors,
760+
// but needs constructors in this particular case, we miss the constructor
761+
// accessor that's produced with an `enteredAfter` in ExplicitOuter, so
762+
// `tranformInfo` of the constructor in erasure yields a method type without
763+
// an outer parameter. We fix this problem by adding the missing outer
764+
// parameter here.
765+
constr.copySymDenotation(
766+
info = outer.addParam(constr.owner.asClass, constr.info)
767+
).installAfter(erasurePhase)
768+
outerParamDefs(constr)
769+
else Nil
770+
749771
override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = {
750772
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
751773
var implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)

compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ object ExplicitOuter {
240240
needsOuterIfReferenced(cls) && outerAccessor(cls).exists
241241

242242
/** Class constructor takes an outer argument. Can be called only after phase ExplicitOuter. */
243-
private def hasOuterParam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
243+
def hasOuterParam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
244244
!cls.is(Trait) && needsOuterIfReferenced(cls) && outerAccessor(cls).exists
245245

246246
/** Tree references an outer class of `cls` which is not a static owner.
@@ -412,14 +412,5 @@ object ExplicitOuter {
412412
loop(start, count)
413413
catch case ex: ClassCastException =>
414414
throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls")
415-
416-
/** The outer parameter definition of a constructor if it needs one */
417-
def paramDefs(constr: Symbol): List[ValDef] =
418-
if (constr.isConstructor && hasOuterParam(constr.owner.asClass)) {
419-
val MethodTpe(outerName :: _, outerType :: _, _) = constr.info
420-
val outerSym = ctx.newSymbol(constr, outerName, Param, outerType)
421-
ValDef(outerSym) :: Nil
422-
}
423-
else Nil
424415
}
425416
}

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ object RefChecks {
472472
}
473473
}
474474

475-
def ignoreDeferred(mbr: Symbol) =
475+
def ignoreDeferred(mbr: Symbol) =
476476
mbr.isType
477477
|| mbr.isSuperAccessor // not yet synthesized
478478
|| mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr)
@@ -619,15 +619,16 @@ object RefChecks {
619619
// (3) is violated but not (2).
620620
def checkNoAbstractDecls(bc: Symbol): Unit = {
621621
for (decl <- bc.info.decls)
622-
if (decl.is(Deferred) && !ignoreDeferred(decl)) {
622+
if (decl.is(Deferred)) {
623623
val impl = decl.matchingMember(clazz.thisType)
624-
if (impl == NoSymbol || (decl.owner isSubClass impl.owner)) {
624+
if (impl == NoSymbol || decl.owner.isSubClass(impl.owner))
625+
&& !ignoreDeferred(decl)
626+
then
625627
val impl1 = clazz.thisType.nonPrivateMember(decl.name) // DEBUG
626628
ctx.log(i"${impl1}: ${impl1.info}") // DEBUG
627629
ctx.log(i"${clazz.thisType.memberInfo(decl)}") // DEBUG
628630
abstractClassError(false, "there is a deferred declaration of " + infoString(decl) +
629631
" which is not implemented in a subclass" + err.abstractVarMessage(decl))
630-
}
631632
}
632633
if (bc.asClass.superClass.is(Abstract))
633634
checkNoAbstractDecls(bc.asClass.superClass)

tests/run/i8425.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
text: 22
2+
text: 65
3+
text: 10

tests/run/i8425.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import java.io.{ OutputStream, PrintStream }
2+
3+
trait T {
4+
val text: String
5+
val stream = new PrintStream(new OutputStream {
6+
def write(b: Int) = Console.println(s"text: $b")
7+
}) {
8+
override def println(x: Any) = ???
9+
}
10+
}
11+
12+
@main def Test =
13+
val t = new T { val text = "hello" }
14+
t.stream.write(22)
15+
t.stream.println('A')

0 commit comments

Comments
 (0)