Skip to content

Commit 04b0e85

Browse files
committed
Merge pull request #1288 from dotty-staging/fix/implicit-caching-2
Implicit scope caching: bug fixes and performance improvements
2 parents 030ff82 + 295c981 commit 04b0e85

File tree

8 files changed

+103
-15
lines changed

8 files changed

+103
-15
lines changed

src/dotty/tools/dotc/core/Types.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,7 +2145,12 @@ object Types {
21452145
unique(new CachedAndType(tp1, tp2))
21462146
}
21472147
def make(tp1: Type, tp2: Type)(implicit ctx: Context): Type =
2148-
if (tp1 eq tp2) tp1 else apply(tp1, tp2)
2148+
if ((tp1 eq tp2) || (tp2 eq defn.AnyType))
2149+
tp1
2150+
else if (tp1 eq defn.AnyType)
2151+
tp2
2152+
else
2153+
apply(tp1, tp2)
21492154
}
21502155

21512156
abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType {
@@ -3450,7 +3455,7 @@ object Types {
34503455
case TypeBounds(_, hi) =>
34513456
apply(x, hi)
34523457
case tp: ThisType =>
3453-
apply(x, tp.underlying)
3458+
apply(x, tp.tref)
34543459
case tp: ConstantType =>
34553460
apply(x, tp.underlying)
34563461
case tp: MethodParam =>

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,11 +1146,17 @@ trait Applications extends Compatibility { self: Typer =>
11461146
alts
11471147
}
11481148

1149-
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] =
1150-
alts filter ( alt =>
1151-
if (!ctx.isAfterTyper) isApplicable(alt, targs, args, resultType)
1152-
else isDirectlyApplicable(alt, targs, args, resultType)
1149+
def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = {
1150+
val alts2 = alts.filter(alt =>
1151+
isDirectlyApplicable(alt, targs, args, resultType)
11531152
)
1153+
if (alts2.isEmpty && !ctx.isAfterTyper)
1154+
alts.filter(alt =>
1155+
isApplicable(alt, targs, args, resultType)
1156+
)
1157+
else
1158+
alts2
1159+
}
11541160

11551161
val alts1 = narrowBySize(alts)
11561162
//ctx.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %")

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

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ object Implicits {
106106
*/
107107
class OfTypeImplicits(tp: Type, val companionRefs: TermRefSet)(initctx: Context) extends ImplicitRefs(initctx) {
108108
assert(initctx.typer != null)
109-
val refs: List[TermRef] = {
109+
lazy val refs: List[TermRef] = {
110110
val buf = new mutable.ListBuffer[TermRef]
111111
for (companion <- companionRefs) buf ++= companion.implicitMembers
112112
buf.toList
@@ -284,10 +284,13 @@ trait ImplicitRunInfo { self: RunInfo =>
284284
override implicit protected val ctx: Context = liftingCtx
285285
override def stopAtStatic = true
286286
def apply(tp: Type) = tp match {
287+
case tp: TypeRef if tp.symbol.isLambdaTrait =>
288+
defn.AnyType
287289
case tp: TypeRef if tp.symbol.isAbstractOrAliasType =>
288290
val pre = tp.prefix
289291
def joinClass(tp: Type, cls: ClassSymbol) =
290-
AndType(tp, cls.typeRef.asSeenFrom(pre, cls.owner))
292+
if (cls.isLambdaTrait) tp
293+
else AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner))
291294
val lead = if (tp.prefix eq NoPrefix) defn.AnyType else apply(tp.prefix)
292295
(lead /: tp.classSymbols)(joinClass)
293296
case tp: TypeVar =>
@@ -323,7 +326,7 @@ trait ImplicitRunInfo { self: RunInfo =>
323326
def addParentScope(parent: TypeRef): Unit = {
324327
iscopeRefs(parent) foreach addRef
325328
for (param <- parent.typeParams)
326-
comps ++= iscopeRefs(pre.member(param.name).info)
329+
comps ++= iscopeRefs(tp.member(param.name).info)
327330
}
328331
val companion = cls.companionModule
329332
if (companion.exists) addRef(companion.valRef)
@@ -338,8 +341,6 @@ trait ImplicitRunInfo { self: RunInfo =>
338341
}
339342
}
340343

341-
def ofTypeImplicits(comps: TermRefSet) = new OfTypeImplicits(tp, comps)(ctx)
342-
343344
/** The implicit scope of type `tp`
344345
* @param isLifted Type `tp` is the result of a `liftToClasses` application
345346
*/
@@ -349,9 +350,12 @@ trait ImplicitRunInfo { self: RunInfo =>
349350
ctx.typerState.ephemeral = false
350351
try {
351352
val liftedTp = if (isLifted) tp else liftToClasses(tp)
352-
val result =
353-
if (liftedTp ne tp) iscope(liftedTp, isLifted = true)
354-
else ofTypeImplicits(collectCompanions(tp))
353+
val refs =
354+
if (liftedTp ne tp)
355+
iscope(liftedTp, isLifted = true).companionRefs
356+
else
357+
collectCompanions(tp)
358+
val result = new OfTypeImplicits(tp, refs)(ctx)
355359
if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope")
356360
else if (cacheResult) implicitScopeCache(tp) = result
357361
result
@@ -363,7 +367,18 @@ trait ImplicitRunInfo { self: RunInfo =>
363367
computeIScope(cacheResult = false)
364368
else implicitScopeCache get tp match {
365369
case Some(is) => is
366-
case None => computeIScope(cacheResult = seen.isEmpty)
370+
case None =>
371+
// Implicit scopes are tricky to cache because of loops. For example
372+
// in `tests/pos/implicit-scope-loop.scala`, the scope of B contains
373+
// the scope of A which contains the scope of B. We break the loop
374+
// by returning EmptyTermRefSet in `collectCompanions` for types
375+
// that we have already seen, but this means that we cannot cache
376+
// the computed scope of A, it is incomplete.
377+
// Keeping track of exactly where these loops happen would require a
378+
// lot of book-keeping, instead we choose to be conservative and only
379+
// cache scopes before any type has been seen. This is unfortunate
380+
// because loops are very common for types in scala.collection.
381+
computeIScope(cacheResult = seen.isEmpty)
367382
}
368383
}
369384

tests/pos/implicit-scope-loop.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
trait Dummy[T]
2+
3+
4+
trait A[T] extends B
5+
trait B extends Dummy[A[Int]]
6+
object B {
7+
implicit def theB: B = new B {}
8+
implicit def theA: A[Int] = new A[Int] {}
9+
}
10+
11+
object Test {
12+
def getB(implicit b: B) = b
13+
def getA[T](implicit a: A[T]) = a
14+
15+
getB
16+
getA
17+
}

tests/pos/implicit_cache.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class A
2+
object A {
3+
implicit def theA: A = new A
4+
}
5+
class Foo[T]
6+
object Foo {
7+
implicit def theFoo: Foo[A] = new Foo[A]
8+
}
9+
10+
object Test {
11+
def getFooA(implicit foo: Foo[A]) = foo
12+
def getA(implicit a: A) = a
13+
14+
getFooA
15+
getA
16+
}

tests/pos/implicit_tparam.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Foo[T]
2+
class Bar extends Foo[A]
3+
4+
class A
5+
object A {
6+
implicit val bar: Bar = new Bar
7+
}
8+
9+
object Test {
10+
def getBar(implicit bar: Bar) = bar
11+
getBar
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
C1
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class A
2+
class B
3+
4+
class C1 {
5+
def f(x: A): Unit = println("C1")
6+
}
7+
class C2 extends C1 {
8+
def f(x: B): Unit = println("C2")
9+
}
10+
11+
object Test extends C2 {
12+
implicit def a2b(x: A): B = new B
13+
def main(args: Array[String]): Unit = {
14+
f(new A)
15+
}
16+
}

0 commit comments

Comments
 (0)