Skip to content

[WIP] Termination Checking#25404

Draft
OzairFaizan wants to merge 2 commits intoscala:mainfrom
OzairFaizan:feature/termination-check
Draft

[WIP] Termination Checking#25404
OzairFaizan wants to merge 2 commits intoscala:mainfrom
OzairFaizan:feature/termination-check

Conversation

@OzairFaizan
Copy link

@OzairFaizan OzairFaizan commented Feb 27, 2026

Termination Checking

The goal of this PR is to introduce a termination checker phase for the functional subset of Scala.

Currently,

  • Adds the infrastructure:
    • CheckTermination compiler phase, which forbids all recursive calls.
    • @terminates function annotation.
    • -Ycheck-termination compiler flag.
  • Adds simple structural recursion termination check:
    • Looks at matches on inductive types to find sub-terms.
    • Checks that recursive calls are on sub-terms of input arguments.

Additional notes

This is an EPFL Research Project supervised by @mbovel.

@bishabosha
Copy link
Member

I am curious how this will develop, is the goal to support some subset of recursion if the iteration condition can be proven to reach a base case?

@mbovel
Copy link
Member

mbovel commented Feb 28, 2026

I am curious how this will develop, is the goal to support some subset of recursion if the iteration condition can be proven to reach a base case?

Yes, exactly.

We will start with direct structural recursion on inductive types. For example, recursion on a sub-list:

def sum(l: List[Int]): Int =
  l match
    case Nil => 0
    case x :: xs => x + sum(xs) // xs is strictly smaller than l

Later, we could extend this to more general patterns, such as mutual recursion or recursion on numeric arguments.

The main motivation is integration with refinement types. We will need termination guarantees for definitions that appear in erased proofs in order to preserve soundness.

@mbovel
Copy link
Member

mbovel commented Feb 28, 2026

Failing test:

Error:  scala-library-bootstrapped: Failed binary compatibility check against org.scala-lang:scala-library:3.8.0! Found 1 potential problems (filtered 172)
Error:   * class scala.annotation.terminates does not have a correspondent in other version
Error:     filter with: ProblemFilters.exclude[MissingClassProblem]("scala.annotation.terminates")
Error:  
Error:  Filters in MiMaFilters.Scala3Library are used in this check.

You will probably need to add a filter in there:

val ForwardsBreakingChanges: Map[String, Seq[ProblemFilter]] = Map(
// Additions that require a new minor version of the library
Build.mimaPreviousDottyVersion -> Seq(
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.caps.package#package.freeze"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.caps.package#package.cap"),
ProblemFilters.exclude[FinalClassProblem]("scala.Function1$UnliftOps$"),
ProblemFilters.exclude[FinalClassProblem]("scala.jdk.Accumulator$AccumulatorFactoryShape$"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.ArrayOps.iterateUntilEmpty$extension"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.ArrayOps.scala$collection$ArrayOps$$elemTag$extension"),
ProblemFilters.exclude[MissingFieldProblem]("scala.language#experimental.safe"),
ProblemFilters.exclude[MissingClassProblem]("scala.language$experimental$safe$")
))

@noti0na1
Copy link
Member

Later, we could extend this to more general patterns, such as mutual recursion or recursion on numeric arguments.

How do you tell if these is any recursion and check it statically?

@mbovel
Copy link
Member

mbovel commented Feb 28, 2026

How do you tell if these is any recursion and check it statically?

We'd probably need to construct a call graph, and check a measure is decreasing across calls.

We'll try to replicate part of what is done in Lean or Rocq. We're not aiming for full generality of course.

In recursive calls, at least on argument should decrease, and all others
should be nonincreasing.

An argument is decreasing if it is a case class whose `unapply` method
is not overridden, whose fields are immutable and the recursive call is
on a subpart of it.
@OzairFaizan OzairFaizan force-pushed the feature/termination-check branch from d0286d3 to 83fd276 Compare March 1, 2026 20:11
@mbovel mbovel changed the title Termination Checking [WIP] Termination Checking Mar 2, 2026
@mbovel mbovel self-assigned this Mar 2, 2026
@mbovel mbovel added the stat:tbc Work left in unfinished with high chance of being continued and/on useful later on. label Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat:do not merge stat:tbc Work left in unfinished with high chance of being continued and/on useful later on.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants