Skip to content

Commit 9541a15

Browse files
committed
Cache right hand side of parameterless alias implicits
1 parent 0365fd7 commit 9541a15

File tree

7 files changed

+189
-1
lines changed

7 files changed

+189
-1
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Compiler {
6262
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
6363
new ProtectedAccessors, // Add accessors for protected members
6464
new ExtensionMethods, // Expand methods of value classes with extension methods
65+
new CacheAliasImplicits, // Cache RHS of parameterless alias implicits
6566
new ShortcutImplicits, // Allow implicit functions without creating closures
6667
new ByNameClosures, // Expand arguments to by-name parameters to closures
6768
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ object NameKinds {
359359
val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$")
360360

361361
val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
362+
val CacheName = new SuffixNameKind(CACHE, "$_cache")
362363
val DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true }
363364
val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") {
364365
override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ object NameTags extends TastyFormat.NameTags {
3636
final val IMPLMETH = 32 // Used to define methods in implementation classes
3737
// (can probably be removed).
3838

39+
final val CACHE = 33 // Used as a cache for the rhs of an alias implicit.
40+
3941
def nameTagToString(tag: Int): String = tag match {
4042
case UTF8 => "UTF8"
4143
case QUALIFIED => "QUALIFIED"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import MegaPhase._
5+
import core.DenotTransformers.{IdentityDenotTransformer}
6+
import core.Symbols._
7+
import core.Contexts._
8+
import core.Types._
9+
import core.Flags._
10+
import core.StdNames.nme
11+
import core.NameKinds.CacheName
12+
import core.Constants.Constant
13+
import core.Decorators._
14+
import core.TypeErasure.erasure
15+
import ast.tpd
16+
17+
object CacheAliasImplicits {
18+
val name: String = "cacheAliasImplicits"
19+
20+
/** Flags that disable caching */
21+
val NoCacheFlags =
22+
StableRealizable | // It's a simple forwarder, leave it as one
23+
Exported // Export forwarders are never cached
24+
}
25+
26+
/** This phase ensures that the right hand side of parameterless alias implicits
27+
* is cached. It applies to all alias implicits that have neither type parameters
28+
* not a given clause. Example: The alias
29+
*
30+
* implicit a for TC = rhs
31+
*
32+
* is expanded before this phase
33+
*
34+
* implicit def a: TC = rhs
35+
*
36+
* It is then expanded further as follows:
37+
*
38+
* 1. If `rhs` is a simple name `x` (possibly with a `this.` prefix), leave the definition as is.
39+
* 2. Otherwise, if `rhs` is a pure path, replace the definition with
40+
*
41+
* implicit val a: TC = rhs
42+
*
43+
* 3. Otherwise, if `TC` is a reference type, replace the definition with
44+
*
45+
* private[this] var a$_cache: TC = null
46+
* implicit def a: TC = { if (a$_cache == null) a$_cache = rhs; a$_cache }
47+
*
48+
* 4. Otherwise `TC` is a value type. Replace the definition with
49+
*
50+
* lazy implicit val a: TC = rhs
51+
*/
52+
class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { thisPhase =>
53+
import tpd._
54+
55+
override def phaseName: String = CacheAliasImplicits.name
56+
57+
override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = {
58+
val sym = tree.symbol
59+
sym.info match {
60+
case ExprType(rhsType) if sym.is(Implied, butNot = CacheAliasImplicits.NoCacheFlags) =>
61+
// If rhs is a simple TermRef, leave a def.
62+
tree.rhs.tpe match {
63+
case TermRef(pre, _) =>
64+
pre match {
65+
case NoPrefix => return tree
66+
case pre: ThisType if pre.cls == ctx.owner.enclosingClass => return tree
67+
case _ =>
68+
}
69+
case _ =>
70+
}
71+
def makeVal(additionalFlags: FlagSet) = {
72+
sym.copySymDenotation(
73+
initFlags = sym.flags &~ Method | additionalFlags,
74+
info = rhsType)
75+
.installAfter(thisPhase)
76+
cpy.ValDef(tree)(tree.name, tree.tpt, tree.rhs)
77+
}
78+
if (isPurePath(tree.rhs)) makeVal(EmptyFlags)
79+
else if (rhsType.classSymbol.isValueClass ||
80+
!erasure(rhsType).typeSymbol.derivesFrom(defn.ObjectClass)) makeVal(Lazy)
81+
else {
82+
val cacheFlags = if (ctx.owner.isClass) Private | Local | Mutable else Mutable
83+
val cacheSym =
84+
ctx.newSymbol(ctx.owner, CacheName(tree.name), cacheFlags, rhsType, coord = sym.coord)
85+
if (ctx.owner.isClass) cacheSym.enteredAfter(thisPhase)
86+
val cacheDef = ValDef(cacheSym, tpd.defaultValue(rhsType))
87+
val cachingDef = cpy.DefDef(tree)(rhs =
88+
Block(
89+
If(
90+
ref(cacheSym).select(defn.Any_==).appliedTo(nullLiteral),
91+
Assign(ref(cacheSym), tree.rhs),
92+
unitLiteral) :: Nil,
93+
ref(cacheSym)
94+
)
95+
)
96+
Thicket(cacheDef, cachingDef)
97+
}
98+
case _ => tree
99+
}
100+
}
101+
}
102+
103+
104+

docs/docs/reference/contextual-implicit/relationship-implicits.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Implicit instances can be mapped to combinations of implicit objects and implici
3232

3333
- If the right hand side is a simple reference, we can
3434
use a forwarder to that reference without caching it.
35-
- If the right hand side is more complex, but still known to be pure, we can
35+
- If the right hand side is more complex, but still known to be a pure path, we can
3636
create a `val` that computes it ahead of time.
3737

3838
Examples:

tests/run/implicit-alias.check

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
= new
2+
TC
3+
= x
4+
TC
5+
= new VC
6+
TC
7+
TC
8+
= x.y
9+
with given
10+
TC
11+
TC
12+
with type params
13+
TC
14+
TC

tests/run/implicit-alias.scala

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
object Test extends App {
2+
3+
class TC {
4+
println("TC")
5+
}
6+
7+
class TC1
8+
implied for TC1
9+
10+
class TV(val tc: TC) extends AnyVal
11+
12+
trait C {
13+
val x: TC
14+
implied for TC = x
15+
the[TC]
16+
the[TC]
17+
}
18+
class D extends C {
19+
override val x = new TC
20+
}
21+
22+
locally{
23+
println("= new")
24+
implied t for TC = new TC
25+
the[TC]
26+
the[TC]
27+
}
28+
29+
locally {
30+
println("= x")
31+
new D
32+
}
33+
34+
locally{
35+
println("= new VC")
36+
implied t for TV = new TV(new TC)
37+
the[TV]
38+
the[TV]
39+
}
40+
41+
class TCC {
42+
val tc: TC = new TC
43+
}
44+
45+
val tcc = new TCC
46+
locally {
47+
println("= x.y")
48+
implied t for TC = tcc.tc
49+
the[TC]
50+
the[TC]
51+
}
52+
53+
locally {
54+
println("with given")
55+
implied t for TC given TC1 = new TC
56+
the[TC]
57+
the[TC]
58+
}
59+
60+
locally {
61+
println("with type params")
62+
implied t[X] for TC = new TC
63+
the[TC]
64+
the[TC]
65+
}
66+
}

0 commit comments

Comments
 (0)