Skip to content

Commit

Permalink
Fix "forward reference from self constructor" checking
Browse files Browse the repository at this point in the history
The previous check did not consider the case where the self constructor
was itself a block that originated from named and default parameters in
the actual self constructor call. That gave a false negative for some code
in akka. The negative was not discovered before since we did not propagate
the correct context information into the expression of a block.
  • Loading branch information
odersky committed Feb 20, 2022
1 parent a6732dc commit ba96933
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 12 deletions.
43 changes: 31 additions & 12 deletions compiler/src/dotty/tools/dotc/transform/ForwardDepChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 30 additions & 0 deletions tests/neg/complex-self-call.scala
Original file line number Diff line number Diff line change
@@ -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)
29 changes: 29 additions & 0 deletions tests/pos/complex-self-call.scala
Original file line number Diff line number Diff line change
@@ -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"))

0 comments on commit ba96933

Please sign in to comment.