diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 6e198bbeada9..3e0e148f7101 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -24,6 +24,8 @@ import java.io.PrintWriter import xsbti.api.DefinitionType import scala.collection.mutable +import scala.util.hashing.MurmurHash3 +import scala.util.chaining.* /** This phase sends a representation of the API of classes to sbt via callbacks. * @@ -140,9 +142,21 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { /** This cache is necessary to avoid unstable name hashing when `typeCache` is present, * see the comment in the `RefinedType` case in `computeType` * The cache key is (api of RefinedType#parent, api of RefinedType#refinedInfo). - */ + */ private val refinedTypeCache = new mutable.HashMap[(api.Type, api.Definition), api.Structure] + /** This cache is necessary to avoid infinite loops when hashing an inline "Body" annotation. + * Its values are transitively seen inline references within a call chain starting from a single "origin" inline + * definition. Avoid hashing an inline "Body" annotation if its associated definition is already in the cache. + * Precondition: the cache is empty whenever we hash a new "origin" inline "Body" annotation. + */ + private val seenInlineCache = mutable.HashSet.empty[Symbol] + + /** This cache is optional, it avoids recomputing hashes of inline "Body" annotations, + * e.g. when a concrete inline method is inherited by a subclass. + */ + private val inlineBodyCache = mutable.HashMap.empty[Symbol, Int] + private val allNonLocalClassesInSrc = new mutable.HashSet[xsbti.api.ClassLike] private val _mainClasses = new mutable.HashSet[String] @@ -219,7 +233,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { val structure = apiClassStructure(sym) val acc = apiAccess(sym) val modifiers = apiModifiers(sym) - val anns = apiAnnotations(sym).toArray + val anns = apiAnnotations(sym, inlineOrigin = NoSymbol).toArray val topLevel = sym.isTopLevelClass val childrenOfSealedClass = sym.sealedDescendants.sorted(classFirstSort).map(c => if (c.isClass) @@ -320,54 +334,100 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { } } - def apiDefinitions(defs: List[Symbol]): List[api.ClassDefinition] = { - defs.sorted(classFirstSort).map(apiDefinition) - } + def apiDefinitions(defs: List[Symbol]): List[api.ClassDefinition] = + defs.sorted(classFirstSort).map(apiDefinition(_, inlineOrigin = NoSymbol)) - def apiDefinition(sym: Symbol): api.ClassDefinition = { + /** `inlineOrigin` denotes an optional inline method that we are + * currently hashing the body of. If it exists, include extra information + * that is missing after erasure + */ + def apiDefinition(sym: Symbol, inlineOrigin: Symbol): api.ClassDefinition = { if (sym.isClass) { apiClass(sym.asClass) } else if (sym.isType) { apiTypeMember(sym.asType) } else if (sym.is(Mutable, butNot = Accessor)) { api.Var.of(sym.name.toString, apiAccess(sym), apiModifiers(sym), - apiAnnotations(sym).toArray, apiType(sym.info)) + apiAnnotations(sym, inlineOrigin).toArray, apiType(sym.info)) } else if (sym.isStableMember && !sym.isRealMethod) { api.Val.of(sym.name.toString, apiAccess(sym), apiModifiers(sym), - apiAnnotations(sym).toArray, apiType(sym.info)) + apiAnnotations(sym, inlineOrigin).toArray, apiType(sym.info)) } else { - apiDef(sym.asTerm) + apiDef(sym.asTerm, inlineOrigin) } } - def apiDef(sym: TermSymbol): api.Def = { + /** `inlineOrigin` denotes an optional inline method that we are + * currently hashing the body of. If it exists, include extra information + * that is missing after erasure + */ + def apiDef(sym: TermSymbol, inlineOrigin: Symbol): api.Def = { + + var seenInlineExtras = false + var inlineExtras = 41 + + def mixInlineParam(p: Symbol): Unit = + if inlineOrigin.exists && p.is(Inline) then + seenInlineExtras = true + inlineExtras = hashInlineParam(p, inlineExtras) + + def inlineExtrasAnnot: Option[api.Annotation] = + val h = inlineExtras + Option.when(seenInlineExtras) { + marker(s"${MurmurHash3.finalizeHash(h, "inlineExtras".hashCode)}") + } + + def tparamList(pt: TypeLambda): List[api.TypeParameter] = + pt.paramNames.lazyZip(pt.paramInfos).map((pname, pbounds) => + apiTypeParameter(pname.toString, 0, pbounds.lo, pbounds.hi) + ) + + def paramList(mt: MethodType, params: List[Symbol]): api.ParameterList = + val apiParams = params.lazyZip(mt.paramInfos).map((param, ptype) => + mixInlineParam(param) + api.MethodParameter.of( + param.name.toString, apiType(ptype), param.is(HasDefault), api.ParameterModifier.Plain)) + api.ParameterList.of(apiParams.toArray, mt.isImplicitMethod) + def paramLists(t: Type, paramss: List[List[Symbol]]): List[api.ParameterList] = t match { case pt: TypeLambda => paramLists(pt.resultType, paramss.drop(1)) case mt @ MethodTpe(pnames, ptypes, restpe) => assert(paramss.nonEmpty && paramss.head.hasSameLengthAs(pnames), i"mismatch for $sym, ${sym.info}, ${sym.paramSymss}") - val apiParams = paramss.head.lazyZip(ptypes).map((param, ptype) => - api.MethodParameter.of(param.name.toString, apiType(ptype), - param.is(HasDefault), api.ParameterModifier.Plain)) - api.ParameterList.of(apiParams.toArray, mt.isImplicitMethod) - :: paramLists(restpe, paramss.tail) + paramList(mt, paramss.head) :: paramLists(restpe, paramss.tail) case _ => Nil } - val tparams = sym.info match { + /** returns list of pairs of 1: the position in all parameter lists, and 2: a type parameter list */ + def tparamLists(t: Type, index: Int): List[(Int, List[api.TypeParameter])] = t match case pt: TypeLambda => - pt.paramNames.lazyZip(pt.paramInfos).map((pname, pbounds) => - apiTypeParameter(pname.toString, 0, pbounds.lo, pbounds.hi)) + (index, tparamList(pt)) :: tparamLists(pt.resultType, index + 1) + case mt: MethodType => + tparamLists(mt.resultType, index + 1) case _ => Nil - } + + val (tparams, tparamsExtras) = sym.info match + case pt: TypeLambda => + (tparamList(pt), tparamLists(pt.resultType, index = 1)) + case mt: MethodType => + (Nil, tparamLists(mt.resultType, index = 1)) + case _ => + (Nil, Nil) + val vparamss = paramLists(sym.info, sym.paramSymss) val retTp = sym.info.finalResultType.widenExpr + val tparamsExtraAnnot = Option.when(tparamsExtras.nonEmpty) { + marker(s"${hashTparamsExtras(tparamsExtras)("tparamsExtra".hashCode)}") + } + + val annotations = inlineExtrasAnnot ++: tparamsExtraAnnot ++: apiAnnotations(sym, inlineOrigin) + api.Def.of(sym.zincMangledName.toString, apiAccess(sym), apiModifiers(sym), - apiAnnotations(sym).toArray, tparams.toArray, vparamss.toArray, apiType(retTp)) + annotations.toArray, tparams.toArray, vparamss.toArray, apiType(retTp)) } def apiTypeMember(sym: TypeSymbol): api.TypeMember = { @@ -375,7 +435,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { val name = sym.name.toString val access = apiAccess(sym) val modifiers = apiModifiers(sym) - val as = apiAnnotations(sym) + val as = apiAnnotations(sym, inlineOrigin = NoSymbol) val tpe = sym.info if (sym.isAliasType) @@ -585,23 +645,29 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { sym.isOneOf(GivenOrImplicit), sym.is(Lazy), sym.is(Macro), sym.isSuperAccessor) } - def apiAnnotations(s: Symbol): List[api.Annotation] = { + /** `inlineOrigin` denotes an optional inline method that we are + * currently hashing the body of. + */ + def apiAnnotations(s: Symbol, inlineOrigin: Symbol): List[api.Annotation] = { val annots = new mutable.ListBuffer[api.Annotation] val inlineBody = Inliner.bodyToInline(s) - if (!inlineBody.isEmpty) { + if !inlineBody.isEmpty then // If the body of an inline def changes, all the reverse dependencies of // this method need to be recompiled. sbt has no way of tracking method // bodies, so we include the hash of the body of the method as part of the // signature we send to sbt. - // - // FIXME: The API of a class we send to Zinc includes the signatures of - // inherited methods, which means that we repeatedly compute the hash of - // 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) - annots += marker(inlineBodyHash.toString) - } + + def hash[U](inlineOrigin: Symbol): Int = + assert(seenInlineCache.add(s)) // will fail if already seen, guarded by treeHash + treeHash(inlineBody, inlineOrigin) + + val inlineHash = + if inlineOrigin.exists then hash(inlineOrigin) + else inlineBodyCache.getOrElseUpdate(s, hash(inlineOrigin = s).tap(_ => seenInlineCache.clear())) + + annots += marker(inlineHash.toString) + + end if // In the Scala2 ExtractAPI phase we only extract annotations that extend // StaticAnnotation, but in Dotty we currently pickle all annotations so we @@ -619,15 +685,66 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { /** Produce a hash for a tree that is as stable as possible: * it should stay the same across compiler runs, compiler instances, * JVMs, etc. + * + * `inlineOrigin` denotes an optional inline method that we are hashing the body of, where `tree` could be + * its body, or the body of another method referenced in a call chain leading to `inlineOrigin`. + * + * If `inlineOrigin` is NoSymbol, then tree is the tree of an annotation. */ - def treeHash(tree: Tree): Int = - import scala.util.hashing.MurmurHash3 + def treeHash(tree: Tree, inlineOrigin: Symbol): Int = + import core.Constants.* + + 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 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 => + // 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. + h = MurmurHash3.mix(h, apiType(c.typeValue).hashCode) + case _ => + h = MurmurHash3.mix(h, c.value.hashCode) + h + end constantHash + + def cannotHash(what: String, elem: Any, pos: Positioned): Unit = + internalError(i"Don't know how to produce a stable hash for $what", pos.sourcePos) def positionedHash(p: ast.Positioned, initHash: Int): Int = + var h = initHash + p match case p: WithLazyField[?] => p.forceIfLazy case _ => + + if inlineOrigin.exists then + p match + case ref: RefTree @unchecked => + val sym = ref.symbol + if sym.is(Inline, butNot = Param) && !seenInlineCache.contains(sym) then + // 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 + h = MurmurHash3.mix(h, apiDefinition(sym, inlineOrigin).hashCode) + 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 +752,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 +765,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) + cannotHash(what = i"`$elem` of unknown class ${elem.getClass}", elem, tree) h end iteratorHash @@ -681,6 +778,38 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { MurmurHash3.finalizeHash(h, 0) end treeHash + /** Hash secondary type parameters in separate marker annotation. + * We hash them separately because the position of type parameters is important. + */ + private def hashTparamsExtras(tparamsExtras: List[(Int, List[api.TypeParameter])])(initHash: Int): Int = + + def mixTparams(tparams: List[api.TypeParameter])(initHash: Int) = + var h = initHash + var elems = tparams + while elems.nonEmpty do + h = MurmurHash3.mix(h, elems.head.hashCode) + elems = elems.tail + h + + def mixIndexAndTparams(index: Int, tparams: List[api.TypeParameter])(initHash: Int) = + mixTparams(tparams)(MurmurHash3.mix(initHash, index)) + + var h = initHash + var extras = tparamsExtras + var len = 0 + while extras.nonEmpty do + h = mixIndexAndTparams(index = extras.head(0), tparams = extras.head(1))(h) + extras = extras.tail + len += 1 + MurmurHash3.finalizeHash(h, len) + end hashTparamsExtras + + /** Mix in the name hash also because otherwise switching which + * parameter is inline will not affect the hash. + */ + private def hashInlineParam(p: Symbol, h: Int) = + MurmurHash3.mix(p.name.toString.hashCode, MurmurHash3.mix(h, InlineParamHash)) + def apiAnnotation(annot: Annotation): api.Annotation = { // Like with inline defs, the whole body of the annotation and not just its // type is part of its API so we need to store its hash, but Zinc wants us @@ -691,6 +820,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, inlineOrigin = NoSymbol).toString))) } } diff --git a/compiler/src/dotty/tools/dotc/sbt/package.scala b/compiler/src/dotty/tools/dotc/sbt/package.scala index d5f9a289c800..379a2e45ce40 100644 --- a/compiler/src/dotty/tools/dotc/sbt/package.scala +++ b/compiler/src/dotty/tools/dotc/sbt/package.scala @@ -5,6 +5,10 @@ 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 InlineParamHash = 1997 // 302nd prime + extension (sym: Symbol) def constructorName(using Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index cc058cfc2c6e..2d6d1754fbea 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -980,7 +980,7 @@ trait Implicits: trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { record("inferImplicit") assert(ctx.phase.allowsImplicitSearch, - if (argument.isEmpty) i"missing implicit parameter of type $pt after typer" + if (argument.isEmpty) i"missing implicit parameter of type $pt after typer at phase ${ctx.phase.phaseName}" else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}") if pt.unusableForInference diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index d65fd235e0c3..754ce4d0b9d1 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -188,4 +188,3 @@ def byName[T](x: => T): T = x */ extension [T](x: T) transparent inline def asMatchable: x.type & Matchable = x.asInstanceOf[x.type & Matchable] - diff --git a/project/scripts/bootstrapCmdTests b/project/scripts/bootstrapCmdTests index d1ec6138acb3..472426b23b20 100755 --- a/project/scripts/bootstrapCmdTests +++ b/project/scripts/bootstrapCmdTests @@ -96,8 +96,8 @@ IFS=':=' read -ra versionProps < "$cwd/dist/target/pack/VERSION" # temporarily s [ -n ${versionProps[2]} ] || die "Expected non-empty 'version' property in $cwd/dist/target/pack/VERSION" scala_version=${versionProps[2]} -echo "testing -sourcepath with inlining" -# Here we will test that an inline method symbol loaded from the sourcepath (-sourcepath compiler option) +echo "testing -sourcepath with incremental compile: inlining changed inline def into a def" +# Here we will test that a changed inline method symbol loaded from the sourcepath (-sourcepath compiler option) # will have its `defTree` correctly set when its method body is required for inlining. # So far I have not found a way to replicate issue https://github.com/lampepfl/dotty/issues/13994 # with sbt scripted tests, if a way is found, move this test there. @@ -107,3 +107,17 @@ sbt_test_command="++${scala_version}!;clean;prepareSources;compile;copyChanges;c rm -rf "$cwd/tests/cmdTest-sbt-tests/sourcepath-with-inline/target" rm -rf "$cwd/tests/cmdTest-sbt-tests/sourcepath-with-inline/project/target" rm -f "$cwd/tests/cmdTest-sbt-tests/sourcepath-with-inline/src/main/scala/a/zz.scala" + +echo "testing -sourcepath with incremental compile: hashing reference to changed inline def from an inline def" +# Here we will test that a changed inline method symbol loaded from the sourcepath (-sourcepath compiler option) +# will have its `defTree` correctly set when its method body is hashed by extractAPI, when referenced from another +# inline method. +# So far I have not found a way to replicate https://github.com/lampepfl/dotty/pull/12931#discussion_r753212124 +# with sbt scripted tests, if a way is found, move this test there. +cwd=$(pwd) +sbt_test_dir="$cwd/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash" +sbt_test_command="++${scala_version}!;clean;prepareSources;compile;copyChanges;compile" +(cd "$sbt_test_dir" && "$SBT" "$sbt_test_command") +rm -rf "$sbt_test_dir/target" +rm -rf "$sbt_test_dir/project/target" +rm -f "$sbt_test_dir/src/main/scala/a/zz.scala" diff --git a/sbt-test/source-dependencies/extension-change-second-tparams/A.scala b/sbt-test/source-dependencies/extension-change-second-tparams/A.scala new file mode 100644 index 000000000000..9726ab97fa95 --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/A.scala @@ -0,0 +1,7 @@ +object A { + + class Box[T](val value: T) + + extension (box: Box[Int]) def map[I <: Int](f: Int => I): Box[I] = new Box(f(box.value)) + +} diff --git a/sbt-test/source-dependencies/extension-change-second-tparams/B.scala b/sbt-test/source-dependencies/extension-change-second-tparams/B.scala new file mode 100644 index 000000000000..9faedc0666d3 --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/B.scala @@ -0,0 +1,6 @@ +import A.Box +import A.map + +class B { + val n = Box(5).map(x => x * 3) +} diff --git a/sbt-test/source-dependencies/extension-change-second-tparams/build.sbt b/sbt-test/source-dependencies/extension-change-second-tparams/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/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/extension-change-second-tparams/changes/A1.scala b/sbt-test/source-dependencies/extension-change-second-tparams/changes/A1.scala new file mode 100644 index 000000000000..505d037ceb6b --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/changes/A1.scala @@ -0,0 +1,7 @@ +object A { + + class Box[T](val value: T) + + extension (box: Box[Int]) def map[I](f: Int => I): Box[I] = new Box(f(box.value)) + +} diff --git a/sbt-test/source-dependencies/extension-change-second-tparams/project/CompileState.scala b/sbt-test/source-dependencies/extension-change-second-tparams/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/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/extension-change-second-tparams/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/extension-change-second-tparams/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/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/extension-change-second-tparams/test b/sbt-test/source-dependencies/extension-change-second-tparams/test new file mode 100644 index 000000000000..9b96674ac362 --- /dev/null +++ b/sbt-test/source-dependencies/extension-change-second-tparams/test @@ -0,0 +1,7 @@ +> compile +> recordPreviousIterations +# Force recompilation of B because A.map, called by B.n, has changed +$ copy-file changes/A1.scala A.scala +> compile +# 1 to recompile A, then 1 more to recompile B due to A.map change +> checkIterations 2 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/A.scala b/sbt-test/source-dependencies/inline-rec/A.scala new file mode 100644 index 000000000000..6bf7ee29cda2 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/A.scala @@ -0,0 +1,5 @@ +object A { + inline def callInline: Int = inlinedInt + + inline def inlinedInt: Int = 23 +} diff --git a/sbt-test/source-dependencies/inline-rec/B.scala b/sbt-test/source-dependencies/inline-rec/B.scala new file mode 100644 index 000000000000..eed1cb60271e --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/B.scala @@ -0,0 +1,3 @@ +class B { + val n = A.callInline +} diff --git a/sbt-test/source-dependencies/inline-rec/build.sbt b/sbt-test/source-dependencies/inline-rec/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/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/changes/A1.scala b/sbt-test/source-dependencies/inline-rec/changes/A1.scala new file mode 100644 index 000000000000..32941f30d8ab --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/changes/A1.scala @@ -0,0 +1,5 @@ +object A { + inline def callInline: Int = inlinedInt + + inline def inlinedInt: Int = 47 +} diff --git a/sbt-test/source-dependencies/inline-rec/project/CompileState.scala b/sbt-test/source-dependencies/inline-rec/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/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/project/DottyInjectedPlugin.scala b/sbt-test/source-dependencies/inline-rec/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/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/test b/sbt-test/source-dependencies/inline-rec/test new file mode 100644 index 000000000000..1716f03c6c82 --- /dev/null +++ b/sbt-test/source-dependencies/inline-rec/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/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/build.sbt b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/build.sbt new file mode 100644 index 000000000000..4bff160ff55a --- /dev/null +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/build.sbt @@ -0,0 +1,17 @@ +import java.util.Properties + +val prepareSources = taskKey[Unit]("Copy changes to the src directory") +val copyChanges = taskKey[Unit]("Copy changes to the src directory") + +val srcDir = settingKey[File]("The directory to copy changes to") +val changesDir = settingKey[File]("The directory to copy changes from") + +srcDir := (ThisBuild / baseDirectory).value / "src" / "main" / "scala" +changesDir := (ThisBuild / baseDirectory).value / "changes" + +prepareSources := IO.copyFile(changesDir.value / "zz.original.scala", srcDir.value / "a" / "zz.scala") +copyChanges := IO.copyFile(changesDir.value / "zz.new.scala", srcDir.value / "a" / "zz.scala") + +(Compile / scalacOptions) ++= Seq( + "-sourcepath", (Compile / sourceDirectories).value.map(_.getAbsolutePath).distinct.mkString(java.io.File.pathSeparator), +) diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/changes/zz.new.scala b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/changes/zz.new.scala new file mode 100644 index 000000000000..fbf5cf7fb5e0 --- /dev/null +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/changes/zz.new.scala @@ -0,0 +1,7 @@ +package a + +object Foo: // note that `Foo` is defined in `zz.scala` + class Local + inline def foo(using Local): Nothing = + ??? + ??? diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/changes/zz.original.scala b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/changes/zz.original.scala new file mode 100644 index 000000000000..17a7488ccb1a --- /dev/null +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/changes/zz.original.scala @@ -0,0 +1,6 @@ +package a + +object Foo: // note that `Foo` is defined in `zz.scala` + class Local + inline def foo(using Local): Nothing = + ??? diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties new file mode 100644 index 000000000000..10fd9eee04ac --- /dev/null +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.5.5 diff --git a/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/src/main/scala/a/Bar.scala b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/src/main/scala/a/Bar.scala new file mode 100644 index 000000000000..4d4b7eebe09e --- /dev/null +++ b/tests/cmdTest-sbt-tests/sourcepath-with-inline-api-hash/src/main/scala/a/Bar.scala @@ -0,0 +1,5 @@ +package a + +object Bar: + given Foo.Local() + inline def bar = Foo.foo // `Bar.bar` is inline, it will hash the body of `Foo.foo`