Skip to content

Commit 0ebbcff

Browse files
authored
Merge pull request #7006 from dotty-staging/fix-#6009
Fix #6909: Cache alias givens in lazy vals
2 parents 76dabb9 + 6a22d29 commit 0ebbcff

File tree

11 files changed

+95
-115
lines changed

11 files changed

+95
-115
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,10 @@ class JSCodeGen()(implicit ctx: Context) {
404404
case EmptyTree => ()
405405
case dd: DefDef => generatedMethods ++= genMethod(dd)
406406
case _ =>
407-
throw new FatalError("Illegal tree in gen of genInterface(): " + tree)
407+
throw new FatalError(
408+
i"""Illegal tree in gen of genInterface(): $tree
409+
|class = $td
410+
|in ${ctx.compilationUnit}""")
408411
}
409412
}
410413

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

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -243,20 +243,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
243243
case y => y
244244
}
245245

246-
/** The largest subset of {NoInits, PureInterface} that a
247-
* trait or class enclosing this statement can have as flags.
248-
*/
249-
def defKind(tree: Tree)(implicit ctx: Context): FlagSet = unsplice(tree) match {
250-
case EmptyTree | _: Import => NoInitsInterface
251-
case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface
252-
case tree: DefDef =>
253-
if (tree.unforcedRhs == EmptyTree &&
254-
tree.vparamss.forall(_.forall(_.rhs.isEmpty))) NoInitsInterface
255-
else NoInits
256-
case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags
257-
case _ => EmptyFlags
258-
}
259-
260246
/** The largest subset of {NoInits, PureInterface} that a
261247
* trait or class with these parents can have as flags.
262248
*/
@@ -266,12 +252,6 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
266252
case _ :: parents1 => parentsKind(parents1)
267253
}
268254

269-
/** The largest subset of {NoInits, PureInterface} that a
270-
* trait or class with this body can have as flags.
271-
*/
272-
def bodyKind(body: List[Tree])(implicit ctx: Context): FlagSet =
273-
(NoInitsInterface /: body)((fs, stat) => fs & defKind(stat))
274-
275255
/** Checks whether predicate `p` is true for all result parts of this expression,
276256
* where we zoom into Ifs, Matches, and Blocks.
277257
*/
@@ -342,6 +322,28 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
342322
case _ => false
343323
}
344324

325+
/** The largest subset of {NoInits, PureInterface} that a
326+
* trait or class enclosing this statement can have as flags.
327+
*/
328+
def defKind(tree: Tree)(implicit ctx: Context): FlagSet = unsplice(tree) match {
329+
case EmptyTree | _: Import => NoInitsInterface
330+
case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface
331+
case tree: DefDef =>
332+
if (tree.unforcedRhs == EmptyTree &&
333+
tree.vparamss.forall(_.forall(_.rhs.isEmpty))) NoInitsInterface
334+
else if (tree.mods.is(Given) && tree.tparams.isEmpty && tree.vparamss.isEmpty)
335+
EmptyFlags // might become a lazy val: TODO: check whether we need to suppress NoInits once we have new lazy val impl
336+
else NoInits
337+
case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags
338+
case _ => EmptyFlags
339+
}
340+
341+
/** The largest subset of {NoInits, PureInterface} that a
342+
* trait or class with this body can have as flags.
343+
*/
344+
def bodyKind(body: List[Tree])(implicit ctx: Context): FlagSet =
345+
(NoInitsInterface /: body)((fs, stat) => fs & defKind(stat))
346+
345347
// todo: fill with other methods from TreeInfo that only apply to untpd.Tree's
346348
}
347349

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ trait SymDenotations { this: Context =>
102102
}
103103
}
104104

105-
/** Configurable: Accept stale symbol with warning if in IDE */
106-
def staleOK: Boolean = Config.ignoreStaleInIDE && mode.is(Mode.Interactive)
105+
/** Configurable: Accept stale symbol with warning if in IDE
106+
* Always accept stale symbols when testing pickling.
107+
*/
108+
def staleOK: Boolean =
109+
Config.ignoreStaleInIDE && mode.is(Mode.Interactive) ||
110+
settings.YtestPickler.value
107111

108112
/** Possibly accept stale symbol with warning if in IDE */
109113
def acceptStale(denot: SingleDenotation): Boolean =

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,10 @@ object Symbols {
553553

554554
/** This symbol entered into owner's scope (owner must be a class). */
555555
final def entered(implicit ctx: Context): this.type = {
556-
assert(this.owner.isClass, s"symbol ($this) entered the scope of non-class owner ${this.owner}") // !!! DEBUG
557-
this.owner.asClass.enter(this)
558-
if (this.is(Module)) this.owner.asClass.enter(this.moduleClass)
556+
if (this.owner.isClass) {
557+
this.owner.asClass.enter(this)
558+
if (this.is(Module)) this.owner.asClass.enter(this.moduleClass)
559+
}
559560
this
560561
}
561562

@@ -566,14 +567,16 @@ object Symbols {
566567
*/
567568
def enteredAfter(phase: DenotTransformer)(implicit ctx: Context): this.type =
568569
if (ctx.phaseId != phase.next.id) enteredAfter(phase)(ctx.withPhase(phase.next))
569-
else {
570-
if (this.owner.is(Package)) {
571-
denot.validFor |= InitialPeriod
572-
if (this.is(Module)) this.moduleClass.validFor |= InitialPeriod
573-
}
574-
else this.owner.asClass.ensureFreshScopeAfter(phase)
575-
assert(isPrivate || phase.changesMembers, i"$this entered in ${this.owner} at undeclared phase $phase")
576-
entered
570+
else this.owner match {
571+
case owner: ClassSymbol =>
572+
if (owner.is(Package)) {
573+
denot.validFor |= InitialPeriod
574+
if (this.is(Module)) this.moduleClass.validFor |= InitialPeriod
575+
}
576+
else owner.ensureFreshScopeAfter(phase)
577+
assert(isPrivate || phase.changesMembers, i"$this entered in $owner at undeclared phase $phase")
578+
entered
579+
case _ => this
577580
}
578581

579582
/** Remove symbol from scope of owning class */

compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,18 @@ object CacheAliasImplicits {
2727
* is cached. It applies to all alias implicits that have neither type parameters
2828
* nor a given clause. Example: The alias
2929
*
30-
* implicit a for TC = rhs
30+
* given a as TC = rhs
3131
*
32-
* is expanded before this phase
32+
* is expanded before this phase to:
3333
*
3434
* implicit def a: TC = rhs
3535
*
3636
* It is then expanded further as follows:
3737
*
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
38+
* 1. If `rhs` is a simple name `x` (possibly with a `this.` prefix) that
39+
* refers to a value, leave it as is.
4040
*
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
41+
* 2. Otherwise, replace the definition with
4942
*
5043
* lazy implicit val a: TC = rhs
5144
*/
@@ -56,47 +49,27 @@ class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { this
5649

5750
override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = {
5851
val sym = tree.symbol
59-
sym.info match {
60-
case ExprType(rhsType) if sym.is(Given, 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
52+
val isCached = !sym.is(Inline) && {
53+
sym.info match {
54+
case ExprType(resTpe) if sym.is(Given, butNot = CacheAliasImplicits.NoCacheFlags) =>
55+
tree.rhs.tpe match {
56+
case rhsTpe @ TermRef(NoPrefix, _)
57+
if rhsTpe.isStable => false
58+
case rhsTpe @ TermRef(pre: ThisType, _)
59+
if rhsTpe.isStable && pre.cls == sym.owner.enclosingClass => false
60+
case _ => true
61+
}
62+
case _ => false
63+
}
64+
}
65+
if (isCached) {
66+
sym.copySymDenotation(
67+
initFlags = sym.flags &~ Method | Lazy,
68+
info = sym.info.widenExpr)
69+
.installAfter(thisPhase)
70+
cpy.ValDef(tree)(tree.name, tree.tpt, tree.rhs)
9971
}
72+
else tree
10073
}
10174
}
10275

compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase
9191
val argTypeWrtConstr = argType.subst(origParams, allParamRefs(constr.info))
9292
// argType with references to paramRefs of the primary constructor instead of
9393
// local parameter accessors
94-
val meth = ctx.newSymbol(
94+
ctx.newSymbol(
9595
owner = methOwner,
9696
name = SuperArgName.fresh(cls.name.toTermName),
9797
flags = Synthetic | Private | Method | staticFlag,
9898
info = replaceResult(constr.info, argTypeWrtConstr),
99-
coord = constr.coord)
100-
if (methOwner.isClass) meth.enteredAfter(thisPhase) else meth
99+
coord = constr.coord
100+
).enteredAfter(thisPhase)
101101
}
102102

103103
/** Type of a reference implies that it needs to be hoisted */

compiler/src/dotty/tools/dotc/transform/LambdaLift.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,9 @@ object LambdaLift {
300300
proxyMap(owner) = {
301301
for (fv <- freeValues.toList) yield {
302302
val proxyName = newName(fv)
303-
val proxy = ctx.newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord)
304-
if (owner.isClass) proxy.enteredAfter(thisPhase)
303+
val proxy =
304+
ctx.newSymbol(owner, proxyName.asTermName, newFlags, fv.info, coord = fv.coord)
305+
.enteredAfter(thisPhase)
305306
(fv, proxy)
306307
}
307308
}.toMap

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,7 @@ class Namer { typer: Typer =>
11751175
if (isDerivedValueClass(cls)) cls.setFlag(Final)
11761176
cls.info = avoidPrivateLeaks(cls)
11771177
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
1178-
cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest))
1178+
cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest))
11791179
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable)
11801180
processExports(localCtx)
11811181
}

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,15 +2132,13 @@ class Typer extends Namer
21322132
buf += inlineExpansion(mdef1)
21332133
// replace body with expansion, because it will be used as inlined body
21342134
// from separately compiled files - the original BodyAnnotation is not kept.
2135+
case mdef1: TypeDef if mdef1.symbol.is(Enum, butNot = Case) =>
2136+
enumContexts(mdef1.symbol) = ctx
2137+
buf += mdef1
2138+
case EmptyTree =>
2139+
// clashing synthetic case methods are converted to empty trees, drop them here
21352140
case mdef1 =>
2136-
import untpd.modsDeco
2137-
mdef match {
2138-
case mdef: untpd.TypeDef if mdef.mods.isEnumClass =>
2139-
enumContexts(mdef1.symbol) = ctx
2140-
case _ =>
2141-
}
2142-
if (!mdef1.isEmpty) // clashing synthetic case methods are converted to empty trees
2143-
buf += mdef1
2141+
buf += mdef1
21442142
}
21452143
traverse(rest)
21462144
}

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,21 @@ Given instances can be mapped to combinations of implicit objects, classes and i
2828
class ListOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... }
2929
final implicit def ListOrd[T](implicit ord: Ord[T]): ListOrd[T] = new ListOrd[T]
3030
```
31-
3. Alias givens map to implicit methods. If an alias has neither type parameters nor a given clause, its right-hand side is cached in a variable. There are two cases that can be optimized:
32-
33-
- If the right hand side is a simple reference, we can
34-
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
36-
create a `val` that computes it ahead of time.
31+
3. Alias givens map to implicit methods or implicit lazy vals. If an alias has neither type parameters nor a given clause,
32+
it is treated as a lazy val, unless the right hand side is a simple reference, in which case we can use a forwarder to that
33+
reference without caching it.
3734

3835
Examples:
3936

4037
```scala
4138
given global as ExecutionContext = new ForkJoinContext()
42-
given config as Config = default.config
4339

4440
val ctx: Context
4541
given as Context = ctx
4642
```
4743
would map to
4844
```scala
49-
private[this] var global$cache: ExecutionContext | Null = null
50-
final implicit def global: ExecutionContext = {
51-
if (global$cache == null) global$cache = new ForkJoinContext()
52-
global$cache
53-
}
54-
55-
final implicit val config: Config = default.config
56-
45+
final implicit lazy val global: ExecutionContext = new ForkJoinContext()
5746
final implicit def Context_given = ctx
5847
```
5948

tests/pos/i6909.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.compiletime
2+
trait Foo[A]
3+
4+
5+
trait Qux {
6+
given as Foo[Int] = new Foo[Int] {}
7+
}

0 commit comments

Comments
 (0)