Skip to content

Commit ba96933

Browse files
committed
Fix "forward reference from self constructor" checking
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.
1 parent a6732dc commit ba96933

File tree

3 files changed

+90
-12
lines changed

3 files changed

+90
-12
lines changed

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

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,19 +89,38 @@ class ForwardDepChecks extends MiniPhase:
8989
tree
9090
}
9191

92-
override def transformApply(tree: Apply)(using Context): Apply = {
93-
if (isSelfConstrCall(tree)) {
94-
assert(currentLevel.isInstanceOf[LevelInfo], s"${ctx.owner}/" + i"$tree")
95-
val level = currentLevel.asInstanceOf[LevelInfo]
96-
if (level.maxIndex > 0) {
97-
// An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717
98-
report.debuglog("refsym = " + level.refSym)
99-
report.error("forward reference not allowed from self constructor invocation",
100-
ctx.source.atSpan(level.refSpan))
101-
}
102-
}
92+
/** Check that self constructor call does not contain references to vals or defs
93+
* defined later in the secondary constructor's right hand side. This is tricky
94+
* since the complete self constructor might itself be a block that originated from
95+
* expanding named and default parameters. In that case we have to go outwards
96+
* and find the enclosing expression that consists of that block. Test cases in
97+
* {pos,neg}/complex-self-call.scala.
98+
*/
99+
private def checkSelfConstructorCall()(using Context): Unit =
100+
// Find level info corresponding to constructor's RHS. This is the info of the
101+
// outermost context that has the constructor as owner and that has a LevelInfo.
102+
def rhsLevelInfo(using ctx: Context): OptLevelInfo =
103+
if ctx.outer.owner == ctx.owner then
104+
rhsLevelInfo(using ctx.outer) match
105+
case level: LevelInfo => level
106+
case _ => currentLevel
107+
else currentLevel
108+
109+
rhsLevelInfo match
110+
case level: LevelInfo =>
111+
if level.maxIndex > 0 then
112+
report.debuglog("refsym = " + level.refSym.showLocated)
113+
report.error("forward reference not allowed from self constructor invocation",
114+
ctx.source.atSpan(level.refSpan))
115+
case _ =>
116+
assert(false, s"${ctx.owner.showLocated}")
117+
end checkSelfConstructorCall
118+
119+
override def transformApply(tree: Apply)(using Context): Apply =
120+
if (isSelfConstrCall(tree))
121+
assert(ctx.owner.isConstructor)
122+
checkSelfConstructorCall()
103123
tree
104-
}
105124

106125
override def transformNew(tree: New)(using Context): New = {
107126
currentLevel.enterReference(tree.tpe.typeSymbol, tree.span)

tests/neg/complex-self-call.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// An example extracted from akka that demonstrated a spurious
2+
// "forward reference not allowed from self constructor invocation" error.
3+
class Resizer
4+
class SupervisorStrategy
5+
class Pool
6+
object Pool:
7+
def defaultSupervisorStrategy: SupervisorStrategy = ???
8+
object Dispatchers:
9+
def DefaultDispatcherId = ???
10+
object Resizer:
11+
def fromConfig(config: Config): Option[Resizer] = ???
12+
13+
class Config:
14+
def getInt(str: String): Int = ???
15+
def hasPath(str: String): Boolean = ???
16+
17+
final case class BroadcastPool(
18+
nrOfInstances: Int,
19+
val resizer: Option[Resizer] = None,
20+
val supervisorStrategy: SupervisorStrategy = Pool.defaultSupervisorStrategy,
21+
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
22+
val usePoolDispatcher: Boolean = false)
23+
extends Pool:
24+
25+
def this(config: Config) =
26+
this(
27+
nrOfInstances = config.getInt("nr-of-instances"),
28+
resizer = resiz, // error: forward reference not allowed from self constructor invocation
29+
usePoolDispatcher = config.hasPath("pool-dispatcher"))
30+
def resiz = Resizer.fromConfig(config)

tests/pos/complex-self-call.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// An example extracted from akka that demonstrated a spurious
2+
// "forward reference not allowed from self constructor invocation" error.
3+
class Resizer
4+
class SupervisorStrategy
5+
class Pool
6+
object Pool:
7+
def defaultSupervisorStrategy: SupervisorStrategy = ???
8+
object Dispatchers:
9+
def DefaultDispatcherId = ???
10+
object Resizer:
11+
def fromConfig(config: Config): Option[Resizer] = ???
12+
13+
class Config:
14+
def getInt(str: String): Int = ???
15+
def hasPath(str: String): Boolean = ???
16+
17+
final case class BroadcastPool(
18+
nrOfInstances: Int,
19+
val resizer: Option[Resizer] = None,
20+
val supervisorStrategy: SupervisorStrategy = Pool.defaultSupervisorStrategy,
21+
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
22+
val usePoolDispatcher: Boolean = false)
23+
extends Pool:
24+
25+
def this(config: Config) =
26+
this(
27+
nrOfInstances = config.getInt("nr-of-instances"),
28+
resizer = Resizer.fromConfig(config),
29+
usePoolDispatcher = config.hasPath("pool-dispatcher"))

0 commit comments

Comments
 (0)