Skip to content

Commit d0286d3

Browse files
committed
Add termination checking infrastructure
1 parent 680a62d commit d0286d3

File tree

7 files changed

+97
-1
lines changed

7 files changed

+97
-1
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class Compiler {
3535
List(new TyperPhase) :: // Compiler frontend: namer, typer
3636
List(new WInferUnion, // Check for type arguments inferred as union types
3737
CheckUnused.PostTyper(), // Check for unused
38-
CheckShadowing()) :: // Check for shadowed elements
38+
CheckShadowing(), // Check for shadowed elements
39+
CheckTermination()) :: // Check if annotated functions terminate.
3940
List(new YCheckPositions) :: // YCheck positions
4041
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4142
List(new semanticdb.ExtractSemanticInfo) :: // Extract info into .semanticdb files

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ private sealed trait YSettings:
397397
val Yhelp: Setting[Boolean] = BooleanSetting(ForkSetting, "Y", "Print a synopsis of private options.")
398398
val Ycheck: Setting[List[String]] = PhasesSetting(ForkSetting, "Ycheck", "Check the tree at the end of")
399399
val YcheckMods: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycheck-mods", "Check that symbols and their defining trees have modifiers in sync.")
400+
val YcheckTermination: Setting[Boolean] = BooleanSetting(ForkSetting, "Ycheck-termination", "check if annotated functions terminate")
400401
val Ydebug: Setting[Boolean] = BooleanSetting(ForkSetting, "Ydebug", "Increase the quantity of debugging output.")
401402
val YdebugTrace: Setting[Boolean] = BooleanSetting(ForkSetting, "Ydebug-trace", "Trace core operations.")
402403
val YdebugFlags: Setting[Boolean] = BooleanSetting(ForkSetting, "Ydebug-flags", "Print all flags of definitions.")

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,7 @@ class Definitions {
10841084
@tu lazy val SerialVersionUIDAnnot: ClassSymbol = requiredClass("scala.SerialVersionUID")
10851085
@tu lazy val SilentIntoAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into")
10861086
@tu lazy val TailrecAnnot: ClassSymbol = requiredClass("scala.annotation.tailrec")
1087+
@tu lazy val TerminationAnnot: ClassSymbol = requiredClass("scala.annotation.terminates")
10871088
@tu lazy val ThreadUnsafeAnnot: ClassSymbol = requiredClass("scala.annotation.threadUnsafe")
10881089
@tu lazy val ConstructorOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.constructorOnly")
10891090
@tu lazy val CompileTimeOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.compileTimeOnly")
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import ast.tpd
5+
import core.Contexts.{ctx, Context}
6+
import core.Decorators.*
7+
import core.Flags.*
8+
import core.Symbols.*
9+
import MegaPhase.MiniPhase
10+
11+
class CheckTermination extends MiniPhase {
12+
import tpd.*
13+
14+
override def phaseName: String = CheckTermination.name
15+
16+
override def description: String = CheckTermination.description
17+
18+
override def isEnabled(using Context): Boolean = ctx.settings.YcheckTermination.value
19+
20+
override def transformDefDef(tree: DefDef)(using Context): Tree = {
21+
val method = tree.symbol
22+
val mandatory = method.hasAnnotation(defn.TerminationAnnot)
23+
24+
if mandatory && !method.is(Deferred) then {
25+
val checker = new TerminationChecker(method, tree.termParamss.flatten.map(_.symbol))
26+
checker.traverse(tree.rhs)
27+
}
28+
29+
tree
30+
}
31+
32+
private class TerminationChecker(method: Symbol, params: List[Symbol])(using Context) extends TreeTraverser {
33+
34+
override def traverse(tree: Tree)(using Context): Unit =
35+
tree match {
36+
case tree @ Apply(fun, args) if fun.symbol == method =>
37+
report.error(s"Recursive call to ${method.name}", tree.srcPos)
38+
traverseChildren(tree)
39+
case _ => traverseChildren(tree)
40+
}
41+
}
42+
}
43+
44+
object CheckTermination:
45+
val name = "check-termination"
46+
val description = "check if annotated functions terminate"
47+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc. dba Akka
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.annotation
14+
15+
import scala.language.`2.13`
16+
17+
/** A method annotation which verifies that the method will terminate
18+
*/
19+
final class terminates extends StaticAnnotation
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/ycheck-termination-norecursive.scala:8:8 -----------------------------------------------------------
2+
8 | loop(0 :: l) // error
3+
| ^^^^^^^^^^^^
4+
| Recursive call to loop
5+
-- Error: tests/neg/ycheck-termination-norecursive.scala:15:15 ---------------------------------------------------------
6+
15 | x + sum(xs) // error
7+
| ^^^^^^^
8+
| Recursive call to sum
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//> using options -Ycheck-termination
2+
3+
class C {
4+
import scala.annotation.terminates
5+
6+
@terminates
7+
def loop(l: List[Int]): List[Int] =
8+
loop(0 :: l) // error
9+
10+
@terminates
11+
def sum(l: List[Int]): Int =
12+
l match {
13+
case Nil => 0
14+
case x :: xs =>
15+
x + sum(xs) // error
16+
}
17+
18+
}
19+

0 commit comments

Comments
 (0)