From 2e303787a524653eaea655e6b738a29242973d44 Mon Sep 17 00:00:00 2001 From: eugene yokota Date: Fri, 24 Jul 2026 01:42:06 -0400 Subject: [PATCH] [2.x] Allow opt-out of transient warning (#9437) **Problem** We want to opt out of the transient key warning. **Solution** This implements an optout via nowarn annotation. --- build.sbt | 4 +++ .../sbt/internal/util/appmacro/Cont.scala | 13 ++++++-- .../internal/util/appmacro/ContextUtil.scala | 30 +++++++++++++++++++ .../warning-optout/changes/bad1.sbt | 8 +++++ .../warning-optout/changes/bad2.sbt | 7 +++++ .../warning-optout/changes/good.sbt | 7 +++++ .../warning-optout/changes/good2.sbt | 7 +++++ .../warning-optout/project/plugins.sbt | 1 + .../sbt-test/project-load/warning-optout/test | 17 +++++++++++ 9 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 sbt-app/src/sbt-test/project-load/warning-optout/changes/bad1.sbt create mode 100644 sbt-app/src/sbt-test/project-load/warning-optout/changes/bad2.sbt create mode 100644 sbt-app/src/sbt-test/project-load/warning-optout/changes/good.sbt create mode 100644 sbt-app/src/sbt-test/project-load/warning-optout/changes/good2.sbt create mode 100644 sbt-app/src/sbt-test/project-load/warning-optout/project/plugins.sbt create mode 100644 sbt-app/src/sbt-test/project-load/warning-optout/test diff --git a/build.sbt b/build.sbt index b6bdbb3b3..d26763b4c 100644 --- a/build.sbt +++ b/build.sbt @@ -669,6 +669,10 @@ lazy val coreMacrosProj = (project in file("core-macros")) name := "Core Macros", SettingKey[Boolean]("exportPipelining") := false, mimaSettings, + mimaBinaryIssueFilters ++= Seq( + exclude[ReversedMissingMethodProblem]("sbt.internal.util.appmacro.ContextUtil.*"), + exclude[DirectMissingMethodProblem]("sbt.internal.util.appmacro.ContextUtil#Input.*"), + ), ) // Fixes scope=Scope for Setting (core defined in collectionProj) to define the settings system used in build definitions diff --git a/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala b/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala index 464a8ae82..dfdf1dc5b 100644 --- a/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala +++ b/core-macros/src/main/scala/sbt/internal/util/appmacro/Cont.scala @@ -306,7 +306,7 @@ trait Cont: .asExprOf[BuildWideCacheConfiguration] inputs.foreach: input => if !input.isCacheInput then - if !Cont.transientAllowSet(input.sym.name) then + if !Cont.transientAllowSet(input.sym.name) && !input.isWarnSuppressed then report.warning( s"transient key ${input.sym.name} is excluded from the cache input" ) @@ -430,6 +430,7 @@ trait Cont: val WrapOutputName = "wrapOutput_\u2603\u2603" val WrapOutputDirectoryName = "wrapOutputDirectory_\u2603\u2603" + var nowarnQuals: Set[Term] = Set.empty // Called when transforming the tree to add an input. // For `qual` of type F[A], and a `selection` qual.value. val record = [a] => @@ -470,12 +471,18 @@ trait Cont: }.asTerm) case None => oldTree case _ => - // todo cache opt-out attribute - inputBuf += Input(TypeRepr.of[a], qual, replacement, freshName("q")) + inputBuf += Input( + TypeRepr.of[a], + qual, + replacement, + freshName("q"), + isWarnSuppressed = nowarnQuals.contains(qual), + ) oldTree } val exprWithConfig = cacheConfigExprOpt.map(config => '{ $config; $expr }).getOrElse(expr) + nowarnQuals = collectNowarnQuals(exprWithConfig.asTerm) val body = transformWrappers(exprWithConfig.asTerm, record, Symbol.spliceOwner) val r = inputBuf.toList match case Nil => pure(body) 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 b3547d3d6..2b65e4f76 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 @@ -76,11 +76,13 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int): private val cacheLevelSym = Symbol.requiredClass("sbt.util.cacheLevel") private val transientSym = Symbol.requiredClass("scala.transient") + private val nowarnAnnotSym = Symbol.requiredClass("scala.annotation.nowarn") final class Input( val tpe: TypeRepr, val qual: Term, val term: Term, val name: String, + val isWarnSuppressed: Boolean, ): override def toString: String = s"Input($tpe, $qual, $term, $name, $tags)" @@ -194,6 +196,34 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int): def idTransform[F[_]]: TermTransform[F] = in => in + def collectNowarnQuals(tree: Term): Set[Term] = + val result = mutable.HashSet[Term]() + @tailrec def extractQual(t: Term): Unit = t match + case Inlined(_, _, inner) => extractQual(inner) + case Typed(inner, _) => extractQual(inner) + case Apply(TypeApply(Select(_, _), _ :: Nil), qual :: Nil) => result += qual + case Apply(TypeApply(Ident(_), _ :: Nil), qual :: Nil) => result += qual + case _ => () + def targetsTransient(s: String) = s.isEmpty || s.startsWith("msg=transient") + object scanner extends TreeTraverser: + override def traverseTree(t: Tree)(owner: Symbol): Unit = t match + case Typed(inner, tpt) => + tpt.tpe match + case AnnotatedType(_, annot) if annot.tpe.typeSymbol == nowarnAnnotSym => + val isUnfiltered = annot match + case Apply(_, Nil) => true + case Apply(_, Literal(StringConstant(s)) :: Nil) => targetsTransient(s) + case Apply(_, NamedArg(_, Literal(StringConstant(s))) :: Nil) => targetsTransient(s) + case Apply(_, _ :: Nil) => true + case _ => false + if isUnfiltered then extractQual(inner) + case _ => + super.traverseTree(t)(owner) + case _ => super.traverseTree(t)(owner) + end scanner + scanner.traverseTree(tree)(Symbol.spliceOwner) + result.toSet + def collectDefs(tree: Term, isWrapper: (String, TypeRepr, Term) => Boolean): Set[Symbol] = val defs = mutable.HashSet[Symbol]() object traverser extends TreeTraverser: diff --git a/sbt-app/src/sbt-test/project-load/warning-optout/changes/bad1.sbt b/sbt-app/src/sbt-test/project-load/warning-optout/changes/bad1.sbt new file mode 100644 index 000000000..0d8f17286 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/warning-optout/changes/bad1.sbt @@ -0,0 +1,8 @@ +import scala.annotation.nowarn + +lazy val check = taskKey[Unit]("") +check := { + val a = (state.value: @nowarn) + val b = state.value + println("hi") +} diff --git a/sbt-app/src/sbt-test/project-load/warning-optout/changes/bad2.sbt b/sbt-app/src/sbt-test/project-load/warning-optout/changes/bad2.sbt new file mode 100644 index 000000000..7ac6cd872 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/warning-optout/changes/bad2.sbt @@ -0,0 +1,7 @@ +import scala.annotation.nowarn + +lazy val check = taskKey[Unit]("") +check := { + val s = (state.value: @nowarn("msg=unused")) + println("hi") +} diff --git a/sbt-app/src/sbt-test/project-load/warning-optout/changes/good.sbt b/sbt-app/src/sbt-test/project-load/warning-optout/changes/good.sbt new file mode 100644 index 000000000..541f1dc69 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/warning-optout/changes/good.sbt @@ -0,0 +1,7 @@ +import scala.annotation.nowarn + +lazy val check = taskKey[Unit]("") +check := { + val s = (state.value: @nowarn) + println("hi") +} diff --git a/sbt-app/src/sbt-test/project-load/warning-optout/changes/good2.sbt b/sbt-app/src/sbt-test/project-load/warning-optout/changes/good2.sbt new file mode 100644 index 000000000..931c1b7ea --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/warning-optout/changes/good2.sbt @@ -0,0 +1,7 @@ +import scala.annotation.nowarn + +lazy val check = taskKey[Unit]("") +check := { + val s = (state.value: @nowarn("msg=transient key")) + println("hi") +} diff --git a/sbt-app/src/sbt-test/project-load/warning-optout/project/plugins.sbt b/sbt-app/src/sbt-test/project-load/warning-optout/project/plugins.sbt new file mode 100644 index 000000000..7bd8d3d31 --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/warning-optout/project/plugins.sbt @@ -0,0 +1 @@ +Compile / scalacOptions += "-Werror" diff --git a/sbt-app/src/sbt-test/project-load/warning-optout/test b/sbt-app/src/sbt-test/project-load/warning-optout/test new file mode 100644 index 000000000..14b8b09ee --- /dev/null +++ b/sbt-app/src/sbt-test/project-load/warning-optout/test @@ -0,0 +1,17 @@ +$ copy-file changes/good.sbt build.sbt + +> about + +$ copy-file changes/bad1.sbt build.sbt + +-> reload + +$ copy-file changes/bad2.sbt build.sbt + +-> reload + +$ copy-file changes/good2.sbt build.sbt + +> reload + +$ copy-file changes/good.sbt build.sbt