diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 6e198bbeada9..6c65c8f29c9b 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -599,7 +599,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { // an inline def in every class that extends its owner. To avoid this we // could store the hash as an annotation when pickling an inline def // and retrieve it here instead of computing it on the fly. - val inlineBodyHash = treeHash(inlineBody) + val inlineBodyHash = treeHash(inlineBody, inlineSym = s) annots += marker(inlineBodyHash.toString) } @@ -620,14 +620,110 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { * it should stay the same across compiler runs, compiler instances, * JVMs, etc. */ - def treeHash(tree: Tree): Int = + def treeHash(tree: Tree, inlineSym: Symbol): Int = import scala.util.hashing.MurmurHash3 + import core.Constants.* + + val seenInlines = mutable.HashSet.empty[Symbol] + + if inlineSym ne NoSymbol then + seenInlines += inlineSym // do not hash twice a recursive def + + def nameHash(n: Name, initHash: Int): Int = + val h = + if n.isTermName then + MurmurHash3.mix(initHash, TermNameHash) + else + MurmurHash3.mix(initHash, TypeNameHash) + + // The hashCode of the name itself is not stable across compiler instances + MurmurHash3.mix(h, n.toString.hashCode) + end nameHash + + def typeHash(tp: Type, initHash: Int): Int = + // Go through `apiType` to get a value with a stable hash, it'd + // be better to use Murmur here too instead of relying on + // `hashCode`, but that would essentially mean duplicating + // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala + // and at that point we might as well do type hashing on our own + // representation. + var h = initHash + tp match + case ConstantType(c) => + h = constantHash(c, h) + case TypeBounds(lo, hi) => + h = MurmurHash3.mix(h, apiType(lo).hashCode) + h = MurmurHash3.mix(h, apiType(hi).hashCode) + case tp => + h = MurmurHash3.mix(h, apiType(tp).hashCode) + h + end typeHash + + def constantHash(c: Constant, initHash: Int): Int = + var h = MurmurHash3.mix(initHash, c.tag) + c.tag match + case NullTag => + // No value to hash, the tag is enough. + case ClazzTag => + h = typeHash(c.typeValue, h) + case _ => + h = MurmurHash3.mix(h, c.value.hashCode) + h + end constantHash + + /**An inline method that calls another inline method will eventually inline the call + * at a non-inline callsite, in this case if the implementation of the nested call + * changes, then the callsite will have a different API, we should hash the definition + */ + def inlineReferenceHash(ref: Symbol, rhs: Tree, initHash: Int): Int = + var h = initHash + + def paramssHash(paramss: List[List[Symbol]], initHash: Int): Int = paramss match + case Nil :: paramss1 => + paramssHash(paramss1, MurmurHash3.mix(initHash, EmptyParamHash)) + case params :: paramss1 => + var h = initHash + val paramsIt = params.iterator + while paramsIt.hasNext do + val param = paramsIt.next + h = nameHash(param.name, h) + h = typeHash(param.info, h) + if param.is(Inline) then + h = MurmurHash3.mix(h, InlineParamHash) // inline would change the generated code + paramssHash(paramss1, h) + case Nil => + initHash + end paramssHash + + h = paramssHash(ref.paramSymss, h) + h = typeHash(ref.info.finalResultType, h) + positionedHash(rhs, h) + end inlineReferenceHash + + def err(what: String, elem: Any, pos: Positioned, initHash: Int): Int = + internalError(i"Don't know how to produce a stable hash for $what", pos.sourcePos) + MurmurHash3.mix(initHash, elem.toString.hashCode) def positionedHash(p: ast.Positioned, initHash: Int): Int = + var h = initHash + p match case p: WithLazyField[?] => p.forceIfLazy case _ => + + p match + case ref: RefTree @unchecked => + val sym = ref.symbol + if sym.is(Inline, butNot = Param) && !seenInlines.contains(sym) then + seenInlines += sym // dont re-enter hashing this ref + sym.defTree match + case defTree: ValOrDefDef => + h = inlineReferenceHash(sym, defTree.rhs, h) + case _ => + h = err(i"inline method reference `${ref.name}`", ref.name, ref, h) + case _ => + // FIXME: If `p` is a tree we should probably take its type into account // when hashing it, but producing a stable hash for a type is not trivial // since the same type might have multiple representations, for method @@ -635,12 +731,11 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { // in Zinc that generates hashes from that, if we can reliably produce // stable hashes for types ourselves then we could bypass all that and // send Zinc hashes directly. - val h = MurmurHash3.mix(initHash, p.productPrefix.hashCode) + h = MurmurHash3.mix(h, p.productPrefix.hashCode) iteratorHash(p.productIterator, h) end positionedHash def iteratorHash(it: Iterator[Any], initHash: Int): Int = - import core.Constants._ var h = initHash while it.hasNext do it.next() match @@ -649,30 +744,11 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case xs: List[?] => h = iteratorHash(xs.iterator, h) case c: Constant => - h = MurmurHash3.mix(h, c.tag) - c.tag match - case NullTag => - // No value to hash, the tag is enough. - case ClazzTag => - // Go through `apiType` to get a value with a stable hash, it'd - // be better to use Murmur here too instead of relying on - // `hashCode`, but that would essentially mean duplicating - // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala - // and at that point we might as well do type hashing on our own - // representation. - val apiValue = apiType(c.typeValue) - h = MurmurHash3.mix(h, apiValue.hashCode) - case _ => - h = MurmurHash3.mix(h, c.value.hashCode) + h = constantHash(c, h) case n: Name => - // The hashCode of the name itself is not stable across compiler instances - h = MurmurHash3.mix(h, n.toString.hashCode) + h = nameHash(n, h) case elem => - internalError( - i"Don't know how to produce a stable hash for `$elem` of unknown class ${elem.getClass}", - tree.sourcePos) - - h = MurmurHash3.mix(h, elem.toString.hashCode) + h = err(i"`$elem` of unknown class ${elem.getClass}", elem, tree, h) h end iteratorHash @@ -691,6 +767,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { // annotated @org.junit.Test). api.Annotation.of( apiType(annot.tree.tpe), // Used by sbt to find tests to run - Array(api.AnnotationArgument.of("TREE_HASH", treeHash(annot.tree).toString))) + Array(api.AnnotationArgument.of("TREE_HASH", treeHash(annot.tree, inlineSym = NoSymbol).toString))) } } diff --git a/compiler/src/dotty/tools/dotc/sbt/package.scala b/compiler/src/dotty/tools/dotc/sbt/package.scala index d5f9a289c800..99a6b97bddef 100644 --- a/compiler/src/dotty/tools/dotc/sbt/package.scala +++ b/compiler/src/dotty/tools/dotc/sbt/package.scala @@ -5,6 +5,11 @@ import dotty.tools.dotc.core.Symbols.Symbol import dotty.tools.dotc.core.NameOps.stripModuleClassSuffix import dotty.tools.dotc.core.Names.Name +inline val TermNameHash = 1987 // 300th prime +inline val TypeNameHash = 1993 // 301st prime +inline val EmptyParamHash = 1997 // 302nd prime +inline val InlineParamHash = 1999 // 303rd prime + extension (sym: Symbol) def constructorName(using Context) = diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/A.scala b/sbt-test/source-dependencies/inline-rec-change-inline/A.scala new file mode 100644 index 000000000000..d06100a22564 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/A.scala @@ -0,0 +1,5 @@ +object A { + + inline def callInline: Any = B.inlinedAny("yyy") + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/B.scala b/sbt-test/source-dependencies/inline-rec-change-inline/B.scala new file mode 100644 index 000000000000..61e61a620957 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/B.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny(x: String): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/C.scala b/sbt-test/source-dependencies/inline-rec-change-inline/C.scala new file mode 100644 index 000000000000..e6511852afa1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/C.scala @@ -0,0 +1,3 @@ +class C { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/build.sbt b/sbt-test/source-dependencies/inline-rec-change-inline/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/changes/B1.scala b/sbt-test/source-dependencies/inline-rec-change-inline/changes/B1.scala new file mode 100644 index 000000000000..4a1c47d38572 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/changes/B1.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny(inline x: String): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-change-inline/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-change-inline/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-change-inline/test b/sbt-test/source-dependencies/inline-rec-change-inline/test new file mode 100644 index 000000000000..c06f4da8cd78 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-inline/test @@ -0,0 +1,9 @@ +> compile +> recordPreviousIterations +# Force recompilation of A because B.inlinedAny, called by A.callInline, has added +# the inline flag to one of its parameters. +$ copy-file changes/B1.scala B.scala +> compile +# 1 to recompile B, then 1 more to recompile A due to B.inlinedAny change, +# then 1 final compilation to recompile C due to A.callInline change +> checkIterations 3 diff --git a/sbt-test/source-dependencies/inline-rec-change-param/A.scala b/sbt-test/source-dependencies/inline-rec-change-param/A.scala new file mode 100644 index 000000000000..d06100a22564 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/A.scala @@ -0,0 +1,5 @@ +object A { + + inline def callInline: Any = B.inlinedAny("yyy") + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/B.scala b/sbt-test/source-dependencies/inline-rec-change-param/B.scala new file mode 100644 index 000000000000..61e61a620957 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/B.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny(x: String): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/C.scala b/sbt-test/source-dependencies/inline-rec-change-param/C.scala new file mode 100644 index 000000000000..e6511852afa1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/C.scala @@ -0,0 +1,3 @@ +class C { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/build.sbt b/sbt-test/source-dependencies/inline-rec-change-param/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/changes/B1.scala b/sbt-test/source-dependencies/inline-rec-change-param/changes/B1.scala new file mode 100644 index 000000000000..5e0e374ed105 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/changes/B1.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny(x: Any): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-change-param/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-change-param/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-change-param/test b/sbt-test/source-dependencies/inline-rec-change-param/test new file mode 100644 index 000000000000..22468b12f690 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-param/test @@ -0,0 +1,9 @@ +> compile +> recordPreviousIterations +# Force recompilation of A because B.inlinedAny, called by A.callInline, has changed +# the type of its parameters. +$ copy-file changes/B1.scala B.scala +> compile +# 1 to recompile B, then 1 more to recompile A due to B.inlinedAny change, +# then 1 final compilation to recompile C due to A.callInline change +> checkIterations 3 diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/A.scala b/sbt-test/source-dependencies/inline-rec-change-res-transparent/A.scala new file mode 100644 index 000000000000..d06100a22564 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/A.scala @@ -0,0 +1,5 @@ +object A { + + inline def callInline: Any = B.inlinedAny("yyy") + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/B.scala b/sbt-test/source-dependencies/inline-rec-change-res-transparent/B.scala new file mode 100644 index 000000000000..8a04e33a0428 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/B.scala @@ -0,0 +1,5 @@ +object B { + + transparent inline def inlinedAny(x: String): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/C.scala b/sbt-test/source-dependencies/inline-rec-change-res-transparent/C.scala new file mode 100644 index 000000000000..e6511852afa1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/C.scala @@ -0,0 +1,3 @@ +class C { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/build.sbt b/sbt-test/source-dependencies/inline-rec-change-res-transparent/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/changes/B1.scala b/sbt-test/source-dependencies/inline-rec-change-res-transparent/changes/B1.scala new file mode 100644 index 000000000000..6cbe84dbb2b8 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/changes/B1.scala @@ -0,0 +1,5 @@ +object B { + + transparent inline def inlinedAny(x: String): String = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-change-res-transparent/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-change-res-transparent/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res-transparent/test b/sbt-test/source-dependencies/inline-rec-change-res-transparent/test new file mode 100644 index 000000000000..7cd958b92dd6 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res-transparent/test @@ -0,0 +1,9 @@ +> compile +> recordPreviousIterations +# Force recompilation of A because B.inlinedAny, called by A.callInline, has changed +# the type of its result, while being an inline method. +$ copy-file changes/B1.scala B.scala +> compile +# 1 to recompile B, then 1 more to recompile A due to B.inlinedAny change, +# then 1 final compilation to recompile C due to A.callInline change +> checkIterations 3 diff --git a/sbt-test/source-dependencies/inline-rec-change-res/A.scala b/sbt-test/source-dependencies/inline-rec-change-res/A.scala new file mode 100644 index 000000000000..d06100a22564 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/A.scala @@ -0,0 +1,5 @@ +object A { + + inline def callInline: Any = B.inlinedAny("yyy") + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/B.scala b/sbt-test/source-dependencies/inline-rec-change-res/B.scala new file mode 100644 index 000000000000..61e61a620957 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/B.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny(x: String): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/C.scala b/sbt-test/source-dependencies/inline-rec-change-res/C.scala new file mode 100644 index 000000000000..e6511852afa1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/C.scala @@ -0,0 +1,3 @@ +class C { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/build.sbt b/sbt-test/source-dependencies/inline-rec-change-res/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/changes/B1.scala b/sbt-test/source-dependencies/inline-rec-change-res/changes/B1.scala new file mode 100644 index 000000000000..eaeef8d57ece --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/changes/B1.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny(x: String): String = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-change-res/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-change-res/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-change-res/test b/sbt-test/source-dependencies/inline-rec-change-res/test new file mode 100644 index 000000000000..5972abf58136 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-res/test @@ -0,0 +1,9 @@ +> compile +> recordPreviousIterations +# Force recompilation of A because B.inlinedAny, called by A.callInline, has changed +# the type of its result. +$ copy-file changes/B1.scala B.scala +> compile +# 1 to recompile B, then 1 more to recompile A due to B.inlinedAny change, +# then 1 final compilation to recompile C due to A.callInline change +> checkIterations 3 diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/A.scala b/sbt-test/source-dependencies/inline-rec-change-typaram/A.scala new file mode 100644 index 000000000000..fadbc929841a --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/A.scala @@ -0,0 +1,5 @@ +object A { + + inline def callInline: Any = B.inlinedAny(List("yyy")) + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/B.scala b/sbt-test/source-dependencies/inline-rec-change-typaram/B.scala new file mode 100644 index 000000000000..8501c4c676a0 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/B.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny[F[_], T](x: F[T]): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/C.scala b/sbt-test/source-dependencies/inline-rec-change-typaram/C.scala new file mode 100644 index 000000000000..e6511852afa1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/C.scala @@ -0,0 +1,3 @@ +class C { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/build.sbt b/sbt-test/source-dependencies/inline-rec-change-typaram/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/changes/B1.scala b/sbt-test/source-dependencies/inline-rec-change-typaram/changes/B1.scala new file mode 100644 index 000000000000..73e33ccdfd96 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/changes/B1.scala @@ -0,0 +1,5 @@ +object B { + + inline def inlinedAny[F[X] >: List[X] <: List[X], T <: String](x: F[T]): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-change-typaram/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-change-typaram/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-change-typaram/test b/sbt-test/source-dependencies/inline-rec-change-typaram/test new file mode 100644 index 000000000000..7e18665f3c27 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-change-typaram/test @@ -0,0 +1,9 @@ +> compile +> recordPreviousIterations +# Force recompilation of A because B.inlinedAny, called by A.callInline, has changed +# the type of its type parameters. +$ copy-file changes/B1.scala B.scala +> compile +# 1 to recompile B, then 1 more to recompile A due to B.inlinedAny change, +# then 1 final compilation to recompile C due to A.callInline change +> checkIterations 3 diff --git a/sbt-test/source-dependencies/inline-rec-deep/A.scala b/sbt-test/source-dependencies/inline-rec-deep/A.scala new file mode 100644 index 000000000000..799104b58ba3 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/A.scala @@ -0,0 +1,5 @@ +object A { + + inline def callInline: Any = B.delegated + +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/B.scala b/sbt-test/source-dependencies/inline-rec-deep/B.scala new file mode 100644 index 000000000000..4ed6616db186 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/B.scala @@ -0,0 +1,5 @@ +object B { + + inline def delegated: Any = C.inlinedAny(x = "yyy", y = 23) + +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/C.scala b/sbt-test/source-dependencies/inline-rec-deep/C.scala new file mode 100644 index 000000000000..57ce05b0d2cf --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/C.scala @@ -0,0 +1,5 @@ +object C { + + inline def inlinedAny(x: String, y: Int): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/D.scala b/sbt-test/source-dependencies/inline-rec-deep/D.scala new file mode 100644 index 000000000000..3f87831200d3 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/D.scala @@ -0,0 +1,3 @@ +class D { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/build.sbt b/sbt-test/source-dependencies/inline-rec-deep/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/changes/C1.scala b/sbt-test/source-dependencies/inline-rec-deep/changes/C1.scala new file mode 100644 index 000000000000..4456cc87e252 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/changes/C1.scala @@ -0,0 +1,5 @@ +object C { + + inline def inlinedAny(y: Int, x: String): x.type = x + +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-deep/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-deep/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-deep/test b/sbt-test/source-dependencies/inline-rec-deep/test new file mode 100644 index 000000000000..5381ef195868 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-deep/test @@ -0,0 +1,10 @@ +> compile +> recordPreviousIterations +# Force recompilation of B because C.inlinedAny, called by B.delegated, has changed +# the order of its parameters. +$ copy-file changes/C1.scala C.scala +> compile +# 1 to recompile C, then 1 more to recompile B due to C.inlinedAny change, +# then 1 more to recompile A due to B.delegated change, then 1 final compilation +# to recompile D due to A.callInline change +> checkIterations 4 diff --git a/sbt-test/source-dependencies/inline-rec-mut/A.scala b/sbt-test/source-dependencies/inline-rec-mut/A.scala new file mode 100644 index 000000000000..31e281b8028f --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/A.scala @@ -0,0 +1,8 @@ +object A { + inline def isEven(inline x: Int): Boolean = { + inline if x % 2 == 0 then + true + else + !B.isOdd(x) + } +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/B.scala b/sbt-test/source-dependencies/inline-rec-mut/B.scala new file mode 100644 index 000000000000..d01bf3cc7265 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/B.scala @@ -0,0 +1,8 @@ +object B { + inline def isOdd(inline x: Int): Boolean = { + inline if x % 2 != 0 then + true + else + !A.isEven(x) + } +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/C.scala b/sbt-test/source-dependencies/inline-rec-mut/C.scala new file mode 100644 index 000000000000..d286cb59c060 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/C.scala @@ -0,0 +1,3 @@ +class C { + val n = A.isEven(23) +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/build.sbt b/sbt-test/source-dependencies/inline-rec-mut/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/changes/B1.scala b/sbt-test/source-dependencies/inline-rec-mut/changes/B1.scala new file mode 100644 index 000000000000..8849df65f516 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/changes/B1.scala @@ -0,0 +1,9 @@ +object B { + inline def isOdd(inline x: Int): Boolean = { + inline if x % 2 == 0 then + val cached = A.isEven(x) + !(cached || cached) + else + true + } +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-mut/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-mut/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-mut/test b/sbt-test/source-dependencies/inline-rec-mut/test new file mode 100644 index 000000000000..0cca3a11d161 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-mut/test @@ -0,0 +1,11 @@ +> compile +> recordPreviousIterations +# Force recompilation of A because B.isOdd, called by A.isEven, +# has changed, this should force C to recompile +$ copy-file changes/B1.scala B.scala +> compile +# 1 to recompile B, then 1 more to recompile: +# - A due to B.isOdd change +# - B due to A.isEven change +# - C due to A.isEven change +> checkIterations 2 diff --git a/sbt-test/source-dependencies/inline-rec-val/A.scala b/sbt-test/source-dependencies/inline-rec-val/A.scala new file mode 100644 index 000000000000..21c4f75fd4f0 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/A.scala @@ -0,0 +1,5 @@ +object A { + inline def callInline: Int = inlinedInt + + inline val inlinedInt = 23 +} diff --git a/sbt-test/source-dependencies/inline-rec-val/B.scala b/sbt-test/source-dependencies/inline-rec-val/B.scala new file mode 100644 index 000000000000..eed1cb60271e --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/B.scala @@ -0,0 +1,3 @@ +class B { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec-val/build.sbt b/sbt-test/source-dependencies/inline-rec-val/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-test/source-dependencies/inline-rec-val/changes/A1.scala b/sbt-test/source-dependencies/inline-rec-val/changes/A1.scala new file mode 100644 index 000000000000..2c687006f58f --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/changes/A1.scala @@ -0,0 +1,5 @@ +object A { + inline def callInline: Int = inlinedInt + + inline val inlinedInt = 47 +} diff --git a/sbt-test/source-dependencies/inline-rec-val/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec-val/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-test/source-dependencies/inline-rec-val/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec-val/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/source-dependencies/inline-rec-val/test b/sbt-test/source-dependencies/inline-rec-val/test new file mode 100644 index 000000000000..1716f03c6c82 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec-val/test @@ -0,0 +1,7 @@ +> compile +> recordPreviousIterations +# Force recompilation of B because A.inlinedInt, called by A.callInline, has changed +$ copy-file changes/A1.scala A.scala +> compile +# 1 to recompile A, then 1 more to recompile B due to A.inlinedInt change +> checkIterations 2 diff --git a/sbt-test/source-dependencies/inline-rec/B.scala b/sbt-test/source-dependencies/inline-rec/B.scala index 2b4bd09c6e23..eed1cb60271e 100644 --- a/sbt-test/source-dependencies/inline-rec/B.scala +++ b/sbt-test/source-dependencies/inline-rec/B.scala @@ -1,5 +1,3 @@ class B { - def main(args: Array[String]): Unit = { - assert(A.callInline == C.expected) - } + val n = A.callInline } diff --git a/sbt-test/source-dependencies/inline-rec/C.scala b/sbt-test/source-dependencies/inline-rec/C.scala deleted file mode 100644 index 3b32f0026167..000000000000 --- a/sbt-test/source-dependencies/inline-rec/C.scala +++ /dev/null @@ -1,3 +0,0 @@ -object C { - def expected: Int = 23 -} diff --git a/sbt-test/source-dependencies/inline-rec/changes/C1.scala b/sbt-test/source-dependencies/inline-rec/changes/C1.scala deleted file mode 100644 index 377d5add6ca8..000000000000 --- a/sbt-test/source-dependencies/inline-rec/changes/C1.scala +++ /dev/null @@ -1,3 +0,0 @@ -object C { - def expected: Int = 47 -} diff --git a/sbt-test/source-dependencies/inline-rec/dbg.sbt b/sbt-test/source-dependencies/inline-rec/dbg.sbt deleted file mode 100644 index f9a522c9e610..000000000000 --- a/sbt-test/source-dependencies/inline-rec/dbg.sbt +++ /dev/null @@ -1,3 +0,0 @@ -logLevel := Level.Debug -incOptions in ThisBuild ~= { _.withApiDebug(true) } -incOptions in ThisBuild ~= { _.withRelationsDebug(true) }