Skip to content

Fix #8425: Avoid race between erasure and explicit outer #8433

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

Merged
merged 3 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ object Erasure {
if (sym.isEffectivelyErased) erasedDef(sym)
else
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
var vparams = outer.paramDefs(sym)
var vparams = outerParamDefs(sym)
::: ddef.vparamss.flatten.filterConserve(!_.symbol.is(Flags.Erased))

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

/** The outer parameter definition of a constructor if it needs one */
private def outerParamDefs(constr: Symbol)(using ctx: Context): List[ValDef] =
if constr.isConstructor && hasOuterParam(constr.owner.asClass) then
constr.info match
case MethodTpe(outerName :: _, outerType :: _, _) =>
val outerSym = ctx.newSymbol(constr, outerName, Flags.Param, outerType)
ValDef(outerSym) :: Nil
case _ =>
// There's a possible race condition that a constructor was looked at
// after erasure before we had a chance to run ExplicitOuter on its class
// If furthermore the enclosing class does not always have constructors,
// but needs constructors in this particular case, we miss the constructor
// accessor that's produced with an `enteredAfter` in ExplicitOuter, so
// `tranformInfo` of the constructor in erasure yields a method type without
// an outer parameter. We fix this problem by adding the missing outer
// parameter here.
constr.copySymDenotation(
info = outer.addParam(constr.owner.asClass, constr.info)
).installAfter(erasurePhase)
outerParamDefs(constr)
else Nil

override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = {
val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol)
var implClosure @ Closure(_, meth, _) = super.typedClosure(tree, pt)
Expand Down
11 changes: 1 addition & 10 deletions compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ object ExplicitOuter {
needsOuterIfReferenced(cls) && outerAccessor(cls).exists

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

/** Tree references an outer class of `cls` which is not a static owner.
Expand Down Expand Up @@ -412,14 +412,5 @@ object ExplicitOuter {
loop(start, count)
catch case ex: ClassCastException =>
throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls")

/** The outer parameter definition of a constructor if it needs one */
def paramDefs(constr: Symbol): List[ValDef] =
if (constr.isConstructor && hasOuterParam(constr.owner.asClass)) {
val MethodTpe(outerName :: _, outerType :: _, _) = constr.info
val outerSym = ctx.newSymbol(constr, outerName, Param, outerType)
ValDef(outerSym) :: Nil
}
else Nil
}
}
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ object RefChecks {
}
}

def ignoreDeferred(mbr: Symbol) =
def ignoreDeferred(mbr: Symbol) =
mbr.isType
|| mbr.isSuperAccessor // not yet synthesized
|| mbr.is(JavaDefined) && hasJavaErasedOverriding(mbr)
Expand Down Expand Up @@ -619,15 +619,16 @@ object RefChecks {
// (3) is violated but not (2).
def checkNoAbstractDecls(bc: Symbol): Unit = {
for (decl <- bc.info.decls)
if (decl.is(Deferred) && !ignoreDeferred(decl)) {
if (decl.is(Deferred)) {
val impl = decl.matchingMember(clazz.thisType)
if (impl == NoSymbol || (decl.owner isSubClass impl.owner)) {
if (impl == NoSymbol || decl.owner.isSubClass(impl.owner))
&& !ignoreDeferred(decl)
then
val impl1 = clazz.thisType.nonPrivateMember(decl.name) // DEBUG
ctx.log(i"${impl1}: ${impl1.info}") // DEBUG
ctx.log(i"${clazz.thisType.memberInfo(decl)}") // DEBUG
abstractClassError(false, "there is a deferred declaration of " + infoString(decl) +
" which is not implemented in a subclass" + err.abstractVarMessage(decl))
}
}
if (bc.asClass.superClass.is(Abstract))
checkNoAbstractDecls(bc.asClass.superClass)
Expand Down
3 changes: 3 additions & 0 deletions tests/run/i8425.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
text: 22
text: 65
text: 10
15 changes: 15 additions & 0 deletions tests/run/i8425.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import java.io.{ OutputStream, PrintStream }

trait T {
val text: String
val stream = new PrintStream(new OutputStream {
def write(b: Int) = Console.println(s"text: $b")
}) {
override def println(x: Any) = ???
}
}

@main def Test =
val t = new T { val text = "hello" }
t.stream.write(22)
t.stream.println('A')