Skip to content

Commit 76457c8

Browse files
committed
Even more careful handling of tailcalls.
See i321 doc for description of problem and decision taken. Conflicts: src/dotty/tools/dotc/transform/TailRec.scala tests/pos/tailcall/i321.scala
1 parent 2934c4a commit 76457c8

File tree

2 files changed

+51
-16
lines changed

2 files changed

+51
-16
lines changed

src/dotty/tools/dotc/transform/TailRec.scala

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,25 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
7474
final val labelPrefix = "tailLabel"
7575
final val labelFlags = Flags.Synthetic | Flags.Label
7676

77-
private def mkLabel(method: Symbol)(implicit c: Context): TermSymbol = {
77+
private def mkLabel(method: Symbol, abstractOverClass: Boolean)(implicit c: Context): TermSymbol = {
7878
val name = c.freshName(labelPrefix)
7979

80-
c.newSymbol(method, name.toTermName, labelFlags, fullyParameterizedType(method.info, method.enclosingClass.asClass))
80+
c.newSymbol(method, name.toTermName, labelFlags, fullyParameterizedType(method.info, method.enclosingClass.asClass, abstractOverClass))
8181
}
8282

8383
override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
84+
val sym = tree.symbol
8485
tree match {
8586
case dd@DefDef(name, tparams, vparamss0, tpt, rhs0)
86-
if (dd.symbol.isEffectivelyFinal) && !((dd.symbol is Flags.Accessor) || (rhs0 eq EmptyTree) || (dd.symbol is Flags.Label)) =>
87-
val mandatory = dd.symbol.hasAnnotation(defn.TailrecAnnotationClass)
87+
if (sym.isEffectivelyFinal) && !((sym is Flags.Accessor) || (rhs0 eq EmptyTree) || (sym is Flags.Label)) =>
88+
val mandatory = sym.hasAnnotation(defn.TailrecAnnotationClass)
8889
atGroupEnd { implicit ctx: Context =>
8990

9091
cpy.DefDef(dd)(rhs = {
9192

92-
val origMeth = tree.symbol
93-
val label = mkLabel(dd.symbol)
93+
val defIsTopLevel = sym.owner.isClass
94+
val origMeth = sym
95+
val label = mkLabel(sym, abstractOverClass = defIsTopLevel)
9496
val owner = ctx.owner.enclosingClass.asClass
9597
val thisTpe = owner.thisType.widen
9698

@@ -101,16 +103,17 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
101103
// and second one will actually apply,
102104
// now this speculatively transforms tree and throws away result in many cases
103105
val rhsSemiTransformed = {
104-
val transformer = new TailRecElimination(dd.symbol, owner, thisTpe, mandatory, label)
106+
val transformer = new TailRecElimination(origMeth, owner, thisTpe, mandatory, label, abstractOverClass = defIsTopLevel)
105107
val rhs = atGroupEnd(transformer.transform(rhs0)(_))
106108
rewrote = transformer.rewrote
107109
rhs
108110
}
109111

110112
if (rewrote) {
111113
val dummyDefDef = cpy.DefDef(tree)(rhs = rhsSemiTransformed)
112-
val res = fullyParameterizedDef(label, dummyDefDef)
113-
val call = forwarder(label, dd)
114+
val res = fullyParameterizedDef(label, dummyDefDef, abstractOverClass = defIsTopLevel)
115+
val call = forwarder(label, dd, abstractOverClass = defIsTopLevel)
116+
114117
Block(List(res), call)
115118
} else {
116119
if (mandatory)
@@ -130,7 +133,7 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
130133

131134
}
132135

133-
class TailRecElimination(method: Symbol, enclosingClass: Symbol, thisType: Type, isMandatory: Boolean, label: Symbol) extends tpd.TreeMap {
136+
class TailRecElimination(method: Symbol, enclosingClass: Symbol, thisType: Type, isMandatory: Boolean, label: Symbol, abstractOverClass: Boolean) extends tpd.TreeMap {
134137

135138
import dotty.tools.dotc.ast.tpd._
136139

@@ -179,9 +182,11 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
179182
val targs = typeArguments.map(noTailTransform)
180183
val argumentss = arguments.map(noTailTransforms)
181184

182-
val receiverIsSame = enclosingClass.typeRef.widen =:= recv.tpe.widen
183-
val receiverIsSuper = (method.name eq sym) && enclosingClass.typeRef.widen <:< recv.tpe.widen
184-
val receiverIsThis = recv.tpe.widen =:= thisType
185+
val recvWiden = recv.tpe.widenDealias
186+
187+
val receiverIsSame = enclosingClass.typeRef.widenDealias =:= recvWiden
188+
val receiverIsSuper = (method.name eq sym) && enclosingClass.typeRef.widen <:< recvWiden
189+
val receiverIsThis = recv.tpe =:= thisType
185190

186191
val isRecursiveCall = (method eq sym)
187192

@@ -204,9 +209,13 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete
204209
c.debuglog("Rewriting tail recursive call: " + tree.pos)
205210
rewrote = true
206211
val reciever = noTailTransform(recv)
207-
val classTypeArgs = recv.tpe.baseTypeWithArgs(enclosingClass).argInfos
208-
val trz = classTypeArgs.map(x => ref(x.typeSymbol))
209-
val callTargs: List[tpd.Tree] = targs ::: trz
212+
213+
val callTargs: List[tpd.Tree] =
214+
if (abstractOverClass) {
215+
val classTypeArgs = recv.tpe.baseTypeWithArgs(enclosingClass).argInfos
216+
targs ::: classTypeArgs.map(x => ref(x.typeSymbol))
217+
} else targs
218+
210219
val method = Apply(if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef),
211220
List(reciever))
212221

tests/pos/tailcall/i321.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.annotation.tailrec
2+
/**
3+
* Illustrates that abstracting over type arguments without triggering Ycheck failure is tricky
4+
*
5+
* go1.loop refers to type parameter of i321, and captures value f
6+
* if goo1.loop will abstract over T it will need to cast f or will trigger a Ycheck failure.
7+
* One could decide to not abstract over type parameters in tail calls, but this leads us to go2 example
8+
*
9+
* In go2 we should abstract over i321.T, as we need to change it in recursive call.
10+
*
11+
* For now decision is such - we will abstract for top-level methods, but will not for inner ones.
12+
*/
13+
14+
class i321[T >: Null <: AnyRef] {
15+
16+
def go1(f: T => Int): Int = {
17+
@tailrec def loop(pending: T): Int = {
18+
val head1 = f(pending)
19+
loop(pending)
20+
}
21+
loop(null)
22+
}
23+
24+
final def go2[U >: Null <: AnyRef](t: i321[U]): Int = t.go2(this)
25+
26+
}

0 commit comments

Comments
 (0)