Skip to content

Commit

Permalink
Optimize tailcalls even called with different type arguments.
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasMikula committed Sep 9, 2017
1 parent e1e8d05 commit 4194290
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 17 deletions.
18 changes: 13 additions & 5 deletions src/compiler/scala/tools/nsc/transform/TailCalls.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ abstract class TailCalls extends Transform {
* <p>
* A method call is self-recursive if it calls the current method and
* the method is final (otherwise, it could
* be a call to an overridden method in a subclass). Furthermore, If
* the method has type parameters, the call must contain these
* parameters as type arguments. Recursive calls on a different instance
* be a call to an overridden method in a subclass). Furthermore, if
* the method has type parameters, the recursive call must contain these
* parameters as type arguments, unless the tailcall optimization is
* explicitly requested with the `@tailrec` annotation.
* Recursive calls on a different instance
* are optimized. Since 'this' is not a local variable, a dummy local val
* is added and used as a label parameter. The backend knows to load
* the corresponding argument in the 'this' (local at index 0). This dummy local
Expand Down Expand Up @@ -228,7 +230,11 @@ abstract class TailCalls extends Transform {
def receiverIsSuper = ctx.enclosingType.widen <:< receiver.tpe.widen
def isRecursiveCall = (ctx.method eq fun.symbol) && ctx.tailPos
def transformArgs = if (mustTransformArgs) noTailTransforms(args) else args
def matchesTypeArgs = ctx.tparams sameElements (targs map (_.tpe.typeSymbol))
def sameTypeArgs = ctx.tparams sameElements (targs map (_.tpe.typeSymbol))
def matchingTypeArgs = ctx.tparams.corresponds(targs)((p, a) => !isSpecialized(p) || p == a.tpe.typeSymbol)

def isSpecialized(tparam: Symbol) =
tparam.hasAnnotation(SpecializedClass)

/* Records failure reason in Context for reporting.
* Position is unchanged (by default, the method definition.)
Expand Down Expand Up @@ -258,7 +264,9 @@ abstract class TailCalls extends Transform {
failHere("it contains a recursive call targeting a supertype")
else failHere(defaultReason)
}
else if (!matchesTypeArgs) failHere("it is called recursively with different type arguments")
else if (!sameTypeArgs && !ctx.isMandatory)
failHere("it is called recursively with different type arguments")
else if (!matchingTypeArgs) failHere("it is called recursively with different specialized type arguments")
else if (receiver == EmptyTree) rewriteTailCall(This(currentClass))
else if (!receiverIsSame) failHere("it changes type of 'this' on a polymorphic recursive call")
else rewriteTailCall(receiver)
Expand Down
5 changes: 1 addition & 4 deletions test/files/neg/t6574.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
t6574.scala:4: error: could not optimize @tailrec annotated method notTailPos$extension: it contains a recursive call not in tail position
println("tail")
^
t6574.scala:8: error: could not optimize @tailrec annotated method differentTypeArgs$extension: it is called recursively with different type arguments
{(); new Bad[String, Unit](0)}.differentTypeArgs
^
two errors found
one error found
4 changes: 0 additions & 4 deletions test/files/neg/t6574.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,4 @@ class Bad[X, Y](val v: Int) extends AnyVal {
this.notTailPos[Z](a)(b)
println("tail")
}

@annotation.tailrec final def differentTypeArgs {
{(); new Bad[String, Unit](0)}.differentTypeArgs
}
}
6 changes: 3 additions & 3 deletions test/files/neg/tailrec.check
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ tailrec.scala:50: error: could not optimize @tailrec annotated method fail1: it
tailrec.scala:53: error: could not optimize @tailrec annotated method fail2: it contains a recursive call not in tail position
@tailrec final def fail2[T](xs: List[T]): List[T] = xs match {
^
tailrec.scala:59: error: could not optimize @tailrec annotated method fail3: it is called recursively with different type arguments
@tailrec final def fail3[T](x: Int): Int = fail3(x - 1)
^
tailrec.scala:59: error: could not optimize @tailrec annotated method fail3: it is called recursively with different specialized type arguments
@tailrec final def fail3[@specialized(Int) T](x: Int): Int = fail3(x - 1)
^
tailrec.scala:63: error: could not optimize @tailrec annotated method fail4: it changes type of 'this' on a polymorphic recursive call
@tailrec final def fail4[U](other: Tom[U], x: Int): Int = other.fail4[U](other, x - 1)
^
Expand Down
2 changes: 1 addition & 1 deletion test/files/neg/tailrec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Failures {
}

// unsafe
@tailrec final def fail3[T](x: Int): Int = fail3(x - 1)
@tailrec final def fail3[@specialized(Int) T](x: Int): Int = fail3(x - 1)

// unsafe
class Tom[T](x: Int) {
Expand Down
4 changes: 4 additions & 0 deletions test/files/pos/t6574.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class Bad[X, Y](val v: Int) extends AnyVal {
@annotation.tailrec final def dependent[Z](a: Int)(b: String): b.type = {
this.dependent[Z](a)(b)
}

@annotation.tailrec final def differentTypeArgs {
{(); new Bad[String, Unit](0)}.differentTypeArgs
}
}

class HK[M[_]](val v: Int) extends AnyVal {
Expand Down
13 changes: 13 additions & 0 deletions test/files/pos/t9647.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
sealed trait HList
case class HCons[H, T <: HList](head: H, tail: T) extends HList
case object HNil extends HList

object Test {

@annotation.tailrec
def foo[L <: HList](l: L): Unit = l match {
case HNil => ()
case HCons(h, t) => foo(t)
}

}
6 changes: 6 additions & 0 deletions test/files/run/tailcalls.check
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ test FancyTailCalls.tcInIfCond was successful
test FancyTailCalls.tcInPatternGuard was successful
test FancyTailCalls.differentInstance was successful
test PolyObject.tramp was successful
test PolyObject.size was successful
test PolyObject.specializedSize[Int] was successful
test PolyObject.specializedSize[String] was successful
#partest avian
test Object .f was successful
test Final .f was successful
Expand Down Expand Up @@ -114,3 +117,6 @@ test FancyTailCalls.tcInIfCond was successful
test FancyTailCalls.tcInPatternGuard was successful
test FancyTailCalls.differentInstance was successful
test PolyObject.tramp was successful
test PolyObject.size was successful
test PolyObject.specializedSize[Int] was successful
test PolyObject.specializedSize[String] was successful
18 changes: 18 additions & 0 deletions test/files/run/tailcalls.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ object PolyObject extends App {
tramp[A](x - 1)
else
0

@annotation.tailrec
def size[A](a: A, len: A => Int, tail: List[Either[String, Int]], acc: Int): Int = {
val acc1 = acc + len(a)
tail match {
case Nil => acc1
case Left(s) :: t => size[String](s, _.length, t, acc1)
case Right(i) :: t => size[Int] (i, _ => 1, t, acc1)
}
}

@annotation.tailrec
def specializedSize[@specialized(Int) A](len: A => Int, as: List[A], acc: Int): Int =
if(as.isEmpty) acc
else specializedSize[A](len, as.tail, acc + len(as.head))
}


Expand Down Expand Up @@ -410,6 +425,9 @@ object Test {
check_success_b("FancyTailCalls.tcInPatternGuard", FancyTailCalls.tcInPatternGuard(max, max), true)
check_success("FancyTailCalls.differentInstance", FancyTailCalls.differentInstance(max, 42), 42)
check_success("PolyObject.tramp", PolyObject.tramp[Int](max), 0)
check_success("PolyObject.size", PolyObject.size[Int](1, _ => 1, (1 to 5000).toList.flatMap(_ => List(Left("hi"), Right(5))), 0), 15001)
check_success("PolyObject.specializedSize[Int]", PolyObject.specializedSize[Int](_ => 1, (1 to 5000).toList, 0), 5000)
check_success("PolyObject.specializedSize[String]", PolyObject.specializedSize[String](_.length, List.fill(5000)("hi"), 0), 10000)
}

// testing explicit tailcalls.
Expand Down

0 comments on commit 4194290

Please sign in to comment.