diff --git a/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala b/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala index e01c975d0f0d..8a07e88eb4f8 100644 --- a/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala +++ b/compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala @@ -89,19 +89,38 @@ class ForwardDepChecks extends MiniPhase: tree } - override def transformApply(tree: Apply)(using Context): Apply = { - if (isSelfConstrCall(tree)) { - assert(currentLevel.isInstanceOf[LevelInfo], s"${ctx.owner}/" + i"$tree") - val level = currentLevel.asInstanceOf[LevelInfo] - if (level.maxIndex > 0) { - // An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717 - report.debuglog("refsym = " + level.refSym) - report.error("forward reference not allowed from self constructor invocation", - ctx.source.atSpan(level.refSpan)) - } - } + /** Check that self constructor call does not contain references to vals or defs + * defined later in the secondary constructor's right hand side. This is tricky + * since the complete self constructor might itself be a block that originated from + * expanding named and default parameters. In that case we have to go outwards + * and find the enclosing expression that consists of that block. Test cases in + * {pos,neg}/complex-self-call.scala. + */ + private def checkSelfConstructorCall()(using Context): Unit = + // Find level info corresponding to constructor's RHS. This is the info of the + // outermost context that has the constructor as owner and that has a LevelInfo. + def rhsLevelInfo(using ctx: Context): OptLevelInfo = + if ctx.outer.owner == ctx.owner then + rhsLevelInfo(using ctx.outer) match + case level: LevelInfo => level + case _ => currentLevel + else currentLevel + + rhsLevelInfo match + case level: LevelInfo => + if level.maxIndex > 0 then + report.debuglog("refsym = " + level.refSym.showLocated) + report.error("forward reference not allowed from self constructor invocation", + ctx.source.atSpan(level.refSpan)) + case _ => + assert(false, s"${ctx.owner.showLocated}") + end checkSelfConstructorCall + + override def transformApply(tree: Apply)(using Context): Apply = + if (isSelfConstrCall(tree)) + assert(ctx.owner.isConstructor) + checkSelfConstructorCall() tree - } override def transformNew(tree: New)(using Context): New = { currentLevel.enterReference(tree.tpe.typeSymbol, tree.span) diff --git a/tests/neg/complex-self-call.scala b/tests/neg/complex-self-call.scala new file mode 100644 index 000000000000..bd9651e8200f --- /dev/null +++ b/tests/neg/complex-self-call.scala @@ -0,0 +1,30 @@ +// An example extracted from akka that demonstrated a spurious +// "forward reference not allowed from self constructor invocation" error. +class Resizer +class SupervisorStrategy +class Pool +object Pool: + def defaultSupervisorStrategy: SupervisorStrategy = ??? +object Dispatchers: + def DefaultDispatcherId = ??? +object Resizer: + def fromConfig(config: Config): Option[Resizer] = ??? + +class Config: + def getInt(str: String): Int = ??? + def hasPath(str: String): Boolean = ??? + +final case class BroadcastPool( + nrOfInstances: Int, + val resizer: Option[Resizer] = None, + val supervisorStrategy: SupervisorStrategy = Pool.defaultSupervisorStrategy, + val routerDispatcher: String = Dispatchers.DefaultDispatcherId, + val usePoolDispatcher: Boolean = false) + extends Pool: + + def this(config: Config) = + this( + nrOfInstances = config.getInt("nr-of-instances"), + resizer = resiz, // error: forward reference not allowed from self constructor invocation + usePoolDispatcher = config.hasPath("pool-dispatcher")) + def resiz = Resizer.fromConfig(config) \ No newline at end of file diff --git a/tests/pos/complex-self-call.scala b/tests/pos/complex-self-call.scala new file mode 100644 index 000000000000..2e0e09c7a3e7 --- /dev/null +++ b/tests/pos/complex-self-call.scala @@ -0,0 +1,29 @@ +// An example extracted from akka that demonstrated a spurious +// "forward reference not allowed from self constructor invocation" error. +class Resizer +class SupervisorStrategy +class Pool +object Pool: + def defaultSupervisorStrategy: SupervisorStrategy = ??? +object Dispatchers: + def DefaultDispatcherId = ??? +object Resizer: + def fromConfig(config: Config): Option[Resizer] = ??? + +class Config: + def getInt(str: String): Int = ??? + def hasPath(str: String): Boolean = ??? + +final case class BroadcastPool( + nrOfInstances: Int, + val resizer: Option[Resizer] = None, + val supervisorStrategy: SupervisorStrategy = Pool.defaultSupervisorStrategy, + val routerDispatcher: String = Dispatchers.DefaultDispatcherId, + val usePoolDispatcher: Boolean = false) + extends Pool: + + def this(config: Config) = + this( + nrOfInstances = config.getInt("nr-of-instances"), + resizer = Resizer.fromConfig(config), + usePoolDispatcher = config.hasPath("pool-dispatcher"))