Skip to content

Commit b3886c3

Browse files
authored
Backport "Fix overcompilation due to unstable context bound desugaring" to LTS (#19132)
Backports #18280 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 87a411c + 64830a4 commit b3886c3

File tree

25 files changed

+198
-46
lines changed

25 files changed

+198
-46
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags
77
import Symbols._, StdNames._, Trees._, ContextOps._
88
import Decorators._, transform.SymUtils._
99
import Annotations.Annotation
10-
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, WildcardParamName}
10+
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
1111
import typer.{Namer, Checking}
1212
import util.{Property, SourceFile, SourcePosition, Chars}
1313
import config.Feature.{sourceVersion, migrateTo3, enabled}
@@ -202,10 +202,14 @@ object desugar {
202202
else vdef1
203203
end valDef
204204

205-
def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] =
206-
for (tpt <- tpts) yield {
205+
def makeImplicitParameters(
206+
tpts: List[Tree], implicitFlag: FlagSet,
207+
mkParamName: () => TermName,
208+
forPrimaryConstructor: Boolean = false
209+
)(using Context): List[ValDef] =
210+
for (tpt, i) <- tpts.zipWithIndex yield {
207211
val paramFlags: FlagSet = if (forPrimaryConstructor) LocalParamAccessor else Param
208-
val epname = EvidenceParamName.fresh()
212+
val epname = mkParamName()
209213
ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | implicitFlag)
210214
}
211215

@@ -239,17 +243,27 @@ object desugar {
239243
val DefDef(_, paramss, tpt, rhs) = meth
240244
val evidenceParamBuf = ListBuffer[ValDef]()
241245

246+
var seenContextBounds: Int = 0
242247
def desugarContextBounds(rhs: Tree): Tree = rhs match
243248
case ContextBounds(tbounds, cxbounds) =>
244249
val iflag = if sourceVersion.isAtLeast(`future`) then Given else Implicit
245250
evidenceParamBuf ++= makeImplicitParameters(
246-
cxbounds, iflag, forPrimaryConstructor = isPrimaryConstructor)
251+
cxbounds, iflag,
252+
// Just like with `makeSyntheticParameter` on nameless parameters of
253+
// using clauses, we only need names that are unique among the
254+
// parameters of the method since shadowing does not affect
255+
// implicit resolution in Scala 3.
256+
mkParamName = () =>
257+
val index = seenContextBounds + 1 // Start at 1 like FreshNameCreator.
258+
val ret = ContextBoundParamName(EmptyTermName, index)
259+
seenContextBounds += 1
260+
ret,
261+
forPrimaryConstructor = isPrimaryConstructor)
247262
tbounds
248263
case LambdaTypeTree(tparams, body) =>
249264
cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body))
250265
case _ =>
251266
rhs
252-
253267
val paramssNoContextBounds =
254268
mapParamss(paramss) {
255269
tparam => cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
@@ -380,11 +394,11 @@ object desugar {
380394
meth.paramss :+ evidenceParams
381395
cpy.DefDef(meth)(paramss = paramss1)
382396

383-
/** The implicit evidence parameters of `meth`, as generated by `desugar.defDef` */
397+
/** The parameters generated from the contextual bounds of `meth`, as generated by `desugar.defDef` */
384398
private def evidenceParams(meth: DefDef)(using Context): List[ValDef] =
385399
meth.paramss.reverse match {
386400
case ValDefs(vparams @ (vparam :: _)) :: _ if vparam.mods.isOneOf(GivenOrImplicit) =>
387-
vparams.takeWhile(_.name.is(EvidenceParamName))
401+
vparams.takeWhile(_.name.is(ContextBoundParamName))
388402
case _ =>
389403
Nil
390404
}
@@ -1500,7 +1514,7 @@ object desugar {
15001514

15011515
def makeContextualFunction(formals: List[Tree], body: Tree, erasedParams: List[Boolean])(using Context): Function = {
15021516
val mods = Given
1503-
val params = makeImplicitParameters(formals, mods)
1517+
val params = makeImplicitParameters(formals, mods, mkParamName = () => ContextFunctionParamName.fresh())
15041518
FunctionWithMods(params, body, Modifiers(mods), erasedParams)
15051519
}
15061520

compiler/src/dotty/tools/dotc/core/NameKinds.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,31 @@ object NameKinds {
278278
if (underlying.isEmpty) "$" + info.num + "$" else super.mkString(underlying, info)
279279
}
280280

281+
/** The name of the term parameter generated for a context bound:
282+
*
283+
* def foo[T: A](...): ...
284+
*
285+
* becomes:
286+
*
287+
* def foo[T](...)(using evidence$1: A[T]): ...
288+
*
289+
* The "evidence$" prefix is a convention copied from Scala 2.
290+
*/
291+
val ContextBoundParamName: UniqueNameKind = new UniqueNameKind("evidence$")
292+
293+
/** The name of an inferred contextual function parameter:
294+
*
295+
* val x: A ?=> B = b
296+
*
297+
* becomes:
298+
*
299+
* val x: A ?=> B = (contextual$1: A) ?=> b
300+
*/
301+
val ContextFunctionParamName: UniqueNameKind = new UniqueNameKind("contextual$")
302+
281303
/** Other unique names */
304+
val CanThrowEvidenceName: UniqueNameKind = new UniqueNameKind("canThrow$")
282305
val TempResultName: UniqueNameKind = new UniqueNameKind("ev$")
283-
val EvidenceParamName: UniqueNameKind = new UniqueNameKind("evidence$")
284306
val DepParamName: UniqueNameKind = new UniqueNameKind("(param)")
285307
val LazyImplicitName: UniqueNameKind = new UniqueNameKind("$_lazy_implicit_$")
286308
val LazyLocalName: UniqueNameKind = new UniqueNameKind("$lzy")

compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ object Scala3:
216216

217217
def isEmptyNumbered: Boolean =
218218
!name.is(NameKinds.WildcardParamName)
219-
&& !name.is(NameKinds.EvidenceParamName)
219+
&& !name.is(NameKinds.ContextBoundParamName)
220+
&& !name.is(NameKinds.ContextFunctionParamName)
220221
&& { name match
221222
case NameKinds.AnyNumberedName(nme.EMPTY, _) => true
222223
case _ => false

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Contexts._
1313
import Types._
1414
import Flags._
1515
import Mode.ImplicitsEnabled
16-
import NameKinds.{LazyImplicitName, EvidenceParamName}
16+
import NameKinds.{LazyImplicitName, ContextBoundParamName}
1717
import Symbols._
1818
import Types._
1919
import Decorators._
@@ -975,7 +975,7 @@ trait Implicits:
975975
def addendum = if (qt1 eq qt) "" else (i"\nWhere $qt is an alias of: $qt1")
976976
i"parameter of ${qual.tpe.widen}$addendum"
977977
case _ =>
978-
i"${ if paramName.is(EvidenceParamName) then "an implicit parameter"
978+
i"${ if paramName.is(ContextBoundParamName) then "a context parameter"
979979
else s"parameter $paramName" } of $methodStr"
980980
}
981981

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,8 +1686,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
16861686
checkInInlineContext("summonFrom", tree.srcPos)
16871687
val cases1 = tree.cases.mapconserve {
16881688
case cdef @ CaseDef(pat @ Typed(Ident(nme.WILDCARD), _), _, _) =>
1689-
// case _ : T --> case evidence$n : T
1690-
cpy.CaseDef(cdef)(pat = untpd.Bind(EvidenceParamName.fresh(), pat))
1689+
// case _ : T --> case _$n : T
1690+
cpy.CaseDef(cdef)(pat = untpd.Bind(WildcardParamName.fresh(), pat))
16911691
case cdef => cdef
16921692
}
16931693
typedMatchFinish(tree, tpd.EmptyTree, defn.ImplicitScrutineeTypeRef, cases1, pt)
@@ -1962,7 +1962,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
19621962
def addCanThrowCapabilities(expr: untpd.Tree, cases: List[CaseDef])(using Context): untpd.Tree =
19631963
def makeCanThrow(tp: Type): untpd.Tree =
19641964
untpd.ValDef(
1965-
EvidenceParamName.fresh(),
1965+
CanThrowEvidenceName.fresh(),
19661966
untpd.TypeTree(defn.CanThrowClass.typeRef.appliedTo(tp)),
19671967
untpd.ref(defn.Compiletime_erasedValue))
19681968
.withFlags(Given | Final | Erased)
@@ -3686,7 +3686,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
36863686
else tree
36873687
else if wtp.isContextualMethod then
36883688
def isContextBoundParams = wtp.stripPoly match
3689-
case MethodType(EvidenceParamName(_) :: _) => true
3689+
case MethodType(ContextBoundParamName(_) :: _) => true
36903690
case _ => false
36913691
if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty
36923692
then // Under future-migration, don't infer implicit arguments yet for parameters

compiler/src/dotty/tools/dotc/util/Signatures.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ object Signatures {
407407
(params :: rest)
408408

409409
def isSyntheticEvidence(name: String) =
410-
if !name.startsWith(NameKinds.EvidenceParamName.separator) then false else
410+
if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else
411411
symbol.paramSymss.flatten.find(_.name.show == name).exists(_.flags.is(Flags.Implicit))
412412

413413
denot.info.stripPoly match

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ class DottyBytecodeTests extends DottyBytecodeTest {
874874
}
875875
}
876876

877-
@Test def freshNames = {
877+
@Test def stableNames = {
878878
val sourceA =
879879
"""|class A {
880880
| def a1[T: Ordering]: Unit = {}
@@ -902,11 +902,11 @@ class DottyBytecodeTests extends DottyBytecodeTest {
902902
s"Method ${mn.name} has parameter $actualName but expected $expectedName")
903903
}
904904

905-
// The fresh name counter should be reset for every compilation unit
905+
// Each definition should get the same names since there's no possible clashes.
906906
assertParamName(a1, "evidence$1")
907-
assertParamName(a2, "evidence$2")
907+
assertParamName(a2, "evidence$1")
908908
assertParamName(b1, "evidence$1")
909-
assertParamName(b2, "evidence$2")
909+
assertParamName(b2, "evidence$1")
910910
}
911911
}
912912

presentation-compiler/src/main/dotty/tools/pc/completions/ScaladocCompletions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ object ScaladocCompletions:
112112
defdef.trailingParamss.flatten.collect {
113113
case param
114114
if !param.symbol.isOneOf(Synthetic) &&
115-
!param.name.is(EvidenceParamName) &&
115+
!param.name.is(ContextBoundParamName) &&
116116
param.symbol != extensionParam =>
117117
param.name.show
118118
}
@@ -121,7 +121,7 @@ object ScaladocCompletions:
121121
case param
122122
if !param.is(Synthetic) &&
123123
!param.isTypeParam &&
124-
!param.name.is(EvidenceParamName) =>
124+
!param.name.is(ContextBoundParamName) =>
125125
param.name.show
126126
}
127127
case other =>

presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import scala.meta.pc.SymbolSearch
99
import dotty.tools.dotc.core.Contexts.Context
1010
import dotty.tools.dotc.core.Flags
1111
import dotty.tools.dotc.core.Flags.*
12-
import dotty.tools.dotc.core.NameKinds.EvidenceParamName
12+
import dotty.tools.dotc.core.NameKinds.ContextBoundParamName
1313
import dotty.tools.dotc.core.NameOps.*
1414
import dotty.tools.dotc.core.Names
1515
import dotty.tools.dotc.core.Names.Name
@@ -270,7 +270,7 @@ class ShortenedTypePrinter(
270270

271271
lazy val implicitEvidenceParams: Set[Symbol] =
272272
implicitParams
273-
.filter(p => p.name.toString.startsWith(EvidenceParamName.separator))
273+
.filter(p => p.name.toString.startsWith(ContextBoundParamName.separator))
274274
.toSet
275275

276276
lazy val implicitEvidencesByTypeParam: Map[Symbol, List[String]] =
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package database
2+
3+
object A {
4+
def wrapper: B.Wrapper = ???
5+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package database
2+
3+
object B {
4+
trait GetValue[T]
5+
6+
object GetValue {
7+
implicit def inst[T]: GetValue[T] = ???
8+
}
9+
10+
class ResultSet {
11+
def getV[A: GetValue]: A = ???
12+
}
13+
14+
trait DBParse[T] {
15+
def apply(rs: ResultSet): T
16+
}
17+
18+
class AVG() {
19+
def call: String = "AVG"
20+
}
21+
22+
object ClientOwnerId {
23+
class CompanyId
24+
25+
def parseClientOwnerId[T: DBParse]: Unit = {}
26+
}
27+
28+
class Wrapper(companyId: ClientOwnerId.CompanyId)
29+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package database
2+
3+
object C {
4+
def foo: Unit = {
5+
val rs: B.ResultSet = ???
6+
rs.getV[String]
7+
}
8+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
scalaVersion := sys.props("plugin.scalaVersion")
2+
3+
import sbt.internal.inc.Analysis
4+
import complete.DefaultParsers._
5+
6+
// Reset compiler iterations, necessary because tests run in batch mode
7+
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
8+
recordPreviousIterations := {
9+
val log = streams.value.log
10+
CompileState.previousIterations = {
11+
val previousAnalysis = (previousCompile in Compile).value.analysis.asScala
12+
previousAnalysis match {
13+
case None =>
14+
log.info("No previous analysis detected")
15+
0
16+
case Some(a: Analysis) => a.compilations.allCompilations.size
17+
}
18+
}
19+
}
20+
21+
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
22+
23+
checkIterations := {
24+
val expected: Int = (Space ~> NatBasic).parsed
25+
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
26+
assert(expected == actual, s"Expected $expected compilations, got $actual")
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package database
2+
3+
object B {
4+
trait GetValue[T]
5+
6+
object GetValue {
7+
implicit def inst[T]: GetValue[T] = ???
8+
}
9+
10+
class ResultSet {
11+
def getV[A: GetValue]: A = ???
12+
}
13+
14+
trait DBParse[T]
15+
16+
class AVG() {
17+
def call: String = "AVG2"
18+
}
19+
20+
object ClientOwnerId {
21+
class CompanyId
22+
23+
def parseClientOwnerId[T: DBParse]: Unit = {}
24+
}
25+
26+
class Wrapper(companyId: ClientOwnerId.CompanyId)
27+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// This is necessary because tests are run in batch mode
2+
object CompileState {
3+
@volatile var previousIterations: Int = -1
4+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
> compile
2+
> recordPreviousIterations
3+
4+
# change only the body of a method
5+
$ copy-file changes/B.scala B.scala
6+
7+
# Only B.scala should be recompiled. Previously, this lead to a subsequent
8+
# compilation round because context bounds were desugared into names unique to
9+
# the whole compilation unit, and in the first `compile` the two context bounds
10+
# of B.scala were desugared into `evidence$2` and `evidence$1` in this order
11+
# (because the definitions were visited out of order), but in the second call
12+
# to `compile` we traverse them in order as we typecheck B.scala and ended up
13+
# with `evidence$1` and `evidence$2` instead.
14+
> compile
15+
> checkIterations 1

scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ trait ClassLikeSupport:
578578
val baseTypeRepr = typeForClass(c).memberType(symbol)
579579

580580
def isSyntheticEvidence(name: String) =
581-
if !name.startsWith(NameKinds.EvidenceParamName.separator) then false else
581+
if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else
582582
// This assumes that every parameter that starts with `evidence$` and is implicit is generated by compiler to desugar context bound.
583583
// Howrever, this is just a heuristic, so
584584
// `def foo[A](evidence$1: ClassTag[A]) = 1`

tests/neg/i10901.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
| [T1, T2]
1313
| (x: BugExp4Point2D.ColumnType[T1])
1414
| (y: BugExp4Point2D.ColumnType[T2])
15-
| (implicit evidence$7: Numeric[T1], evidence$8: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
15+
| (implicit evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
1616
| [T1, T2]
1717
| (x: T1)
1818
| (y: BugExp4Point2D.ColumnType[T2])
19-
| (implicit evidence$5: Numeric[T1], evidence$6: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
19+
| (implicit evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
2020
| both match arguments ((x : BugExp4Point2D.IntT.type))((y : BugExp4Point2D.DoubleT.type))
2121
-- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ----------------------------------------------------------------
2222
48 | val pos4: Point2D[Int,Double] = x º 201.1 // error
@@ -31,8 +31,8 @@
3131
| Ambiguous overload. The overloaded alternatives of method º in object dsl with types
3232
| [T1, T2]
3333
| (x: BugExp4Point2D.ColumnType[T1])
34-
| (y: T2)(implicit evidence$9: Numeric[T1], evidence$10: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
35-
| [T1, T2](x: T1)(y: T2)(implicit evidence$3: Numeric[T1], evidence$4: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
34+
| (y: T2)(implicit evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
35+
| [T1, T2](x: T1)(y: T2)(implicit evidence$1: Numeric[T1], evidence$2: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2]
3636
| both match arguments ((x : BugExp4Point2D.IntT.type))((201.1d : Double))
3737
-- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ----------------------------------------------------------------
3838
62 | val y = "abc".foo // error

0 commit comments

Comments
 (0)