Skip to content

Fix #11861 - hash nested calls to inline definitions #12931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 188 additions & 59 deletions compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/sbt/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion library/src/scala/compiletime/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]

18 changes: 16 additions & 2 deletions project/scripts/bootstrapCmdTests
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"
Original file line number Diff line number Diff line change
@@ -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))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import A.Box
import A.map

class B {
val n = Box(5).map(x => x * 3)
}
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This is necessary because tests are run in batch mode
object CompileState {
@volatile var previousIterations: Int = -1
}
Original file line number Diff line number Diff line change
@@ -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")
)
}
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-inline/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object A {

inline def callInline: Any = B.inlinedAny("yyy")

}
5 changes: 5 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-inline/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

inline def inlinedAny(x: String): x.type = x

}
3 changes: 3 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-inline/C.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class C {
val n = A.callInline
}
25 changes: 25 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-inline/build.sbt
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

inline def inlinedAny(inline x: String): x.type = x

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This is necessary because tests are run in batch mode
object CompileState {
@volatile var previousIterations: Int = -1
}
Original file line number Diff line number Diff line change
@@ -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")
)
}
9 changes: 9 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-inline/test
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-param/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object A {

inline def callInline: Any = B.inlinedAny("yyy")

}
5 changes: 5 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-param/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

inline def inlinedAny(x: String): x.type = x

}
3 changes: 3 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-param/C.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class C {
val n = A.callInline
}
25 changes: 25 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-param/build.sbt
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

inline def inlinedAny(x: Any): x.type = x

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This is necessary because tests are run in batch mode
object CompileState {
@volatile var previousIterations: Int = -1
}
Original file line number Diff line number Diff line change
@@ -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")
)
}
9 changes: 9 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-param/test
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object A {

inline def callInline: Any = B.inlinedAny("yyy")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

transparent inline def inlinedAny(x: String): x.type = x

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class C {
val n = A.callInline
}
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

transparent inline def inlinedAny(x: String): String = x

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This is necessary because tests are run in batch mode
object CompileState {
@volatile var previousIterations: Int = -1
}
Original file line number Diff line number Diff line change
@@ -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")
)
}
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-res/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object A {

inline def callInline: Any = B.inlinedAny("yyy")

}
5 changes: 5 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-res/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object B {

inline def inlinedAny(x: String): x.type = x

}
3 changes: 3 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-res/C.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class C {
val n = A.callInline
}
25 changes: 25 additions & 0 deletions sbt-test/source-dependencies/inline-rec-change-res/build.sbt
Original file line number Diff line number Diff line change
@@ -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")
}
Loading