-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix overcompilation due to unstable context bound desugaring
Context bounds are desugared into term parameters `evidence$N` and before this commit, the `N` was chosen to be unique in the current compilation unit. This isn't great because it means that adding a new definition with a context bound in the middle of a file would change the desugaring of subsequent definitions in the same file. Even worse, when using incremental compilation we could end up with the same context bound desugared with a different value of `N` on different compilation runs because the order in which a compilation unit is traversed during Typer is not fixed but depends on the how the units that are jointly compiled depend on each other (as demonstrated by the `stable-ctx-bounds` test). This issue affects all fresh names generated during Typer, but it is especially problematic for context bounds because they're part of the API and renaming a method parameter forces the recompilation of all files calling that method. To fix this, we now only require context bounds parameters to have unique names among all the parameters of the method. This matches how we already desugar `def foo(using A, B)` into `def foo(using x$1: A, x$2: B)` regardless of the context. Note that fresh names used in other situations are still problematic for deterministic compilation. Most of the time they're not part of the API checked by Zinc, but they can still lead to overcompilation if they appear in an `inline def` since the entire body of the `inline def` constitutes its API. In the future, we should follow Scala 2's lead and only require names to be fresh at the method level: scala/scala#6300 (The Scala 2 logic is slightly more complex to handle macros, but I don't think that applies to Scala 3 macros), see #7661. Fixes #18080. [Cherry-picked f322b7b]
- Loading branch information
Showing
10 changed files
with
133 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package database | ||
|
||
object A { | ||
def wrapper: B.Wrapper = ??? | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package database | ||
|
||
object B { | ||
trait GetValue[T] | ||
|
||
object GetValue { | ||
implicit def inst[T]: GetValue[T] = ??? | ||
} | ||
|
||
class ResultSet { | ||
def getV[A: GetValue]: A = ??? | ||
} | ||
|
||
trait DBParse[T] { | ||
def apply(rs: ResultSet): T | ||
} | ||
|
||
class AVG() { | ||
def call: String = "AVG" | ||
} | ||
|
||
object ClientOwnerId { | ||
class CompanyId | ||
|
||
def parseClientOwnerId[T: DBParse]: Unit = {} | ||
} | ||
|
||
class Wrapper(companyId: ClientOwnerId.CompanyId) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package database | ||
|
||
object C { | ||
def foo: Unit = { | ||
val rs: B.ResultSet = ??? | ||
rs.getV[String] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
scalaVersion := sys.props("plugin.scalaVersion") | ||
|
||
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") | ||
} |
27 changes: 27 additions & 0 deletions
27
sbt-test/source-dependencies/stable-ctx-bounds/changes/B.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package database | ||
|
||
object B { | ||
trait GetValue[T] | ||
|
||
object GetValue { | ||
implicit def inst[T]: GetValue[T] = ??? | ||
} | ||
|
||
class ResultSet { | ||
def getV[A: GetValue]: A = ??? | ||
} | ||
|
||
trait DBParse[T] | ||
|
||
class AVG() { | ||
def call: String = "AVG2" | ||
} | ||
|
||
object ClientOwnerId { | ||
class CompanyId | ||
|
||
def parseClientOwnerId[T: DBParse]: Unit = {} | ||
} | ||
|
||
class Wrapper(companyId: ClientOwnerId.CompanyId) | ||
} |
4 changes: 4 additions & 0 deletions
4
sbt-test/source-dependencies/stable-ctx-bounds/project/CompileState.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
> compile | ||
> recordPreviousIterations | ||
|
||
# change only the body of a method | ||
$ copy-file changes/B.scala B.scala | ||
|
||
# Only B.scala should be recompiled. Previously, this lead to a subsequent | ||
# compilation round because context bounds were desugared into names unique to | ||
# the whole compilation unit, and in the first `compile` the two context bounds | ||
# of B.scala were desugared into `evidence$2` and `evidence$1` in this order | ||
# (because the definitions were visited out of order), but in the second call | ||
# to `compile` we traverse them in order as we typecheck B.scala and ended up | ||
# with `evidence$1` and `evidence$2` instead. | ||
> compile | ||
> checkIterations 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters