From c0fe4dfb104a699535c5861e2b64e7504c080c5f Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Sun, 11 Jan 2026 16:33:45 +0900 Subject: [PATCH] Add tailrec annotation --- .../main/scala/sbt/internal/util/appmacro/ContextUtil.scala | 3 +++ .../main/scala/sbt/internal/util/complete/ProcessError.scala | 3 +++ internal/util-complete/src/test/scala/ParserTest.scala | 3 +++ .../src/main/scala/sbt/internal/util/EscHelpers.scala | 3 +++ .../src/main/scala/sbt/internal/util/LoggerWriter.scala | 3 +++ .../internal/librarymanagement/cross/CrossVersionUtil.scala | 3 +++ .../main/scala/sbt/librarymanagement/ConfigurationExtra.scala | 2 ++ lm-coursier/src/main/scala/lmcoursier/Inputs.scala | 2 ++ main-command/src/main/scala/xsbt/IPC.scala | 1 + main-settings/src/main/scala/sbt/std/KeyMacro.scala | 3 +++ main/src/main/scala/sbt/TemplateCommandUtil.scala | 3 +++ main/src/main/scala/sbt/internal/Aggregation.scala | 2 ++ main/src/main/scala/sbt/internal/Script.scala | 2 ++ tasks-standard/src/test/scala/TestRunnerCall.scala | 3 +++ 14 files changed, 36 insertions(+) diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala index db7de1512..aa7b5ad6b 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/ContextUtil.scala @@ -16,6 +16,8 @@ import sbt.util.cacheLevel import sbt.util.CacheLevelTag import xsbti.Attic +import scala.annotation.tailrec + trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int): val qctx: C import qctx.reflect.* @@ -84,6 +86,7 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int): s"Input($tpe, $qual, $term, $name, $tags)" def isCacheInput: Boolean = tags.nonEmpty lazy val tags = extractTags(qual) + @tailrec private def extractTags(tree: Term): List[CacheLevelTag] = def getCacheLevelAnnotation(tree: Term): Option[Term] = Option(tree.tpe.termSymbol) match diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/ProcessError.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/ProcessError.scala index 4e78b7061..f15a70f06 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/ProcessError.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/ProcessError.scala @@ -9,6 +9,8 @@ package sbt.internal.util package complete +import scala.annotation.tailrec + object ProcessError { def apply(command: String, msgs: Seq[String], index: Int): String = { val (line, modIndex) = extractLine(command, index) @@ -24,6 +26,7 @@ object ProcessError { } def takeRightWhile(s: String)(pred: Char => Boolean): String = { + @tailrec def loop(i: Int): String = if (i < 0) s diff --git a/internal/util-complete/src/test/scala/ParserTest.scala b/internal/util-complete/src/test/scala/ParserTest.scala index 29f2acacb..811dc3035 100644 --- a/internal/util-complete/src/test/scala/ParserTest.scala +++ b/internal/util-complete/src/test/scala/ParserTest.scala @@ -9,6 +9,8 @@ package sbt.internal.util package complete +import scala.annotation.tailrec + object JLineTest { import DefaultParsers.* @@ -33,6 +35,7 @@ object JLineTest { val parser = parsers(args(0)) JLineCompletion.installCustomCompletor(reader, parser) + @tailrec def loop(): Unit = { val line = reader.readLine("> ") if (line ne null) { diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/EscHelpers.scala b/internal/util-logging/src/main/scala/sbt/internal/util/EscHelpers.scala index e13c605b3..2d8619beb 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/EscHelpers.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/EscHelpers.scala @@ -8,6 +8,7 @@ package sbt.internal.util +import scala.annotation.tailrec import scala.collection.mutable.ArrayBuffer import scala.util.Try @@ -70,6 +71,7 @@ object EscHelpers { sb.toString } + @tailrec private def nextESC(s: String, start: Int, sb: java.lang.StringBuilder): Unit = { val escIndex = s.indexOf(ESC, start) if (escIndex < 0) { @@ -243,6 +245,7 @@ object EscHelpers { * Skips the escape sequence starting at `i-1`. `i` should be positioned at the character after * the ESC that starts the sequence. */ + @tailrec private def skipESC(s: String, i: Int): Int = { if (i >= s.length) { i diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/LoggerWriter.scala b/internal/util-logging/src/main/scala/sbt/internal/util/LoggerWriter.scala index 0c26a1127..85e304da9 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/LoggerWriter.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/LoggerWriter.scala @@ -10,6 +10,8 @@ package sbt.internal.util import sbt.util.* +import scala.annotation.tailrec + /** * Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at * `level`. A line is delimited by `nl`, which is by default the platform line separator. @@ -48,6 +50,7 @@ class LoggerWriter( process() } + @tailrec private def process(): Unit = { val i = buffer.indexOf(nl) if (i >= 0) { diff --git a/lm-core/src/main/scala/sbt/internal/librarymanagement/cross/CrossVersionUtil.scala b/lm-core/src/main/scala/sbt/internal/librarymanagement/cross/CrossVersionUtil.scala index b5142754f..b5ee4473b 100644 --- a/lm-core/src/main/scala/sbt/internal/librarymanagement/cross/CrossVersionUtil.scala +++ b/lm-core/src/main/scala/sbt/internal/librarymanagement/cross/CrossVersionUtil.scala @@ -1,6 +1,8 @@ package sbt.internal.librarymanagement package cross +import scala.annotation.tailrec + object CrossVersionUtil { val trueString = "true" val falseString = "false" @@ -75,6 +77,7 @@ object CrossVersionUtil { case _ => None } + @tailrec private[sbt] def binaryScala3Version(full: String): String = full match { case ReleaseV(maj, _, _, _) => maj case NonReleaseV_n(maj, min, patch, _) if min.toLong > 0 || patch.toLong > 0 => maj diff --git a/lm-core/src/main/scala/sbt/librarymanagement/ConfigurationExtra.scala b/lm-core/src/main/scala/sbt/librarymanagement/ConfigurationExtra.scala index ff62ede16..8f6a4f743 100644 --- a/lm-core/src/main/scala/sbt/librarymanagement/ConfigurationExtra.scala +++ b/lm-core/src/main/scala/sbt/librarymanagement/ConfigurationExtra.scala @@ -3,6 +3,7 @@ */ package sbt.librarymanagement +import scala.annotation.tailrec import scala.quoted.* object Configurations { @@ -101,6 +102,7 @@ private[librarymanagement] abstract class ConfigurationExtra { private[sbt] object ConfigurationMacro: def configMacroImpl(name: Expr[String])(using Quotes): Expr[Configuration] = import quotes.reflect.* + @tailrec def enclosingTerm(sym: Symbol): Symbol = sym match case sym if sym.flags.is(Flags.Macro) => enclosingTerm(sym.owner) diff --git a/lm-coursier/src/main/scala/lmcoursier/Inputs.scala b/lm-coursier/src/main/scala/lmcoursier/Inputs.scala index d86c156df..561e0df5f 100644 --- a/lm-coursier/src/main/scala/lmcoursier/Inputs.scala +++ b/lm-coursier/src/main/scala/lmcoursier/Inputs.scala @@ -5,6 +5,7 @@ import lmcoursier.definitions.{ Configuration, Module, ModuleName, Organization import sbt.librarymanagement.{ CrossVersion, InclExclRule, ModuleID } import sbt.util.Logger +import scala.annotation.tailrec import scala.collection.mutable object Inputs { @@ -28,6 +29,7 @@ object Inputs { def allExtends(c: Configuration) = { // possibly bad complexity + @tailrec def helper(current: Set[Configuration]): Set[Configuration] = { val newSet = current ++ current.flatMap(configs0.getOrElse(_, Nil)) if ((newSet -- current).nonEmpty) diff --git a/main-command/src/main/scala/xsbt/IPC.scala b/main-command/src/main/scala/xsbt/IPC.scala index e685bb1a6..9fa842c98 100644 --- a/main-command/src/main/scala/xsbt/IPC.scala +++ b/main-command/src/main/scala/xsbt/IPC.scala @@ -34,6 +34,7 @@ object IPC { val random = new java.util.Random def nextPort = random.nextInt(portMax - portMin + 1) + portMin + @tailrec def createServer(attempts: Int): ServerSocket = if (attempts > 0) { try new ServerSocket(nextPort, socketBacklog, loopback) diff --git a/main-settings/src/main/scala/sbt/std/KeyMacro.scala b/main-settings/src/main/scala/sbt/std/KeyMacro.scala index caa38a80d..b18ced36e 100644 --- a/main-settings/src/main/scala/sbt/std/KeyMacro.scala +++ b/main-settings/src/main/scala/sbt/std/KeyMacro.scala @@ -10,6 +10,7 @@ package sbt package std import java.io.File +import scala.annotation.tailrec import scala.quoted.* import scala.reflect.ClassTag @@ -68,6 +69,7 @@ private[sbt] object KeyMacro: private def enclosingTerm(using qctx: Quotes) = import qctx.reflect.* + @tailrec def enclosingTerm0(sym: Symbol): Symbol = sym match case sym if sym.flags.is(Flags.Macro) => enclosingTerm0(sym.owner) @@ -77,6 +79,7 @@ private[sbt] object KeyMacro: private def enclosingClass(using Quotes) = import quotes.reflect.* + @tailrec def rec(sym: Symbol): Symbol = if sym.isClassDef then sym else rec(sym.owner) diff --git a/main/src/main/scala/sbt/TemplateCommandUtil.scala b/main/src/main/scala/sbt/TemplateCommandUtil.scala index aeaff1ae8..0e8c39513 100644 --- a/main/src/main/scala/sbt/TemplateCommandUtil.scala +++ b/main/src/main/scala/sbt/TemplateCommandUtil.scala @@ -24,6 +24,8 @@ import BasicCommandStrings.*, BasicKeys.* import sbt.internal.util.Terminal.hasConsole import sbt.ProjectExtra.* +import scala.annotation.tailrec + private[sbt] object TemplateCommandUtil { def templateCommand: Command = templateCommand0(TemplateCommand) def templateCommandAlias: Command = templateCommand0("init") @@ -206,6 +208,7 @@ private[sbt] object TemplateCommandUtil { private def toLetter(idx: Int): String = nonMoveLetters(idx).toString + @tailrec private def askTemplate(mappingList: List[(String, (String, String))], focus: Int): String = { val msg = "Select a template" displayMappings(mappingList, focus) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index f645ab2c9..0ea22b9f7 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -19,6 +19,7 @@ import sbt.internal.util.* import sbt.internal.client.NetworkClient import sbt.std.Transform.DummyTaskMap import sbt.util.{ Logger, Show } +import scala.annotation.tailrec sealed trait Aggregation object Aggregation { @@ -249,6 +250,7 @@ object Aggregation { extra: BuildUtil[Proj], ): Iterable[ScopedKey[?]] = val mask = ScopeMask() + @tailrec def recur(keys: Set[ScopedKey[?]], acc: Set[ScopedKey[?]]): Set[ScopedKey[?]] = if keys.isEmpty then acc else diff --git a/main/src/main/scala/sbt/internal/Script.scala b/main/src/main/scala/sbt/internal/Script.scala index 9ae66f4b0..a849d4ca0 100644 --- a/main/src/main/scala/sbt/internal/Script.scala +++ b/main/src/main/scala/sbt/internal/Script.scala @@ -22,6 +22,7 @@ import sbt.ProjectExtra.{ extract, setProject } import sbt.SlashSyntax0.* import sbt.io.{ Hash, IO } +import scala.annotation.tailrec object Script { final val Name = "script" @@ -79,6 +80,7 @@ object Script { final case class Block(offset: Int, lines: Seq[String]) def blocks(file: File): Seq[Block] = { val lines = IO.readLines(file).toIndexedSeq + @tailrec def blocks(b: Block, acc: List[Block]): List[Block] = if (b.lines.isEmpty) acc.reverse else { diff --git a/tasks-standard/src/test/scala/TestRunnerCall.scala b/tasks-standard/src/test/scala/TestRunnerCall.scala index af55781ca..dc15b1b2e 100644 --- a/tasks-standard/src/test/scala/TestRunnerCall.scala +++ b/tasks-standard/src/test/scala/TestRunnerCall.scala @@ -12,6 +12,8 @@ import org.scalacheck.* import Prop.* import TaskGen.* +import scala.annotation.tailrec + object TaskRunnerCallTest extends Properties("TaskRunner Call") { property("calculates fibonacci") = forAll(MaxTasksGen, MaxWorkersGen) { (i: Int, workers: Int) => (i > 0) ==> { @@ -36,6 +38,7 @@ object TaskRunnerCallTest extends Properties("TaskRunner Call") { } final def fibDirect(i: Int): Int = { require(i > 0) + @tailrec def build(index: Int, x1: Int, x2: Int): Int = if (index == i) x2