Skip to content

Commit 2ea2c57

Browse files
committed
Non-parametric derived instances now lazy vals
Previously all derived instances would be compiled as given defs, which have caching logic added, where possible, in CacheAliasImplicits. These caches are unsynchronized, which is a reasonable default in circumstances where the progammer has the option to manually insert a lazy val or some other form of synchronization. That option isn't available for derived instances, however, because these givens aren't written explicitly. In this case the more appropriate default is to compile non-parametric derived instances as given lazy vals.
1 parent 76a76ee commit 2ea2c57

File tree

2 files changed

+69
-10
lines changed

2 files changed

+69
-10
lines changed

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

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ trait Deriving { this: Typer =>
5353
// If we set the Synthetic flag here widenDelegate will widen too far and the
5454
// derived instance will have too low a priority to be selected over a freshly
5555
// derived instance at the summoning site.
56+
57+
val (lazyOrMethod, rhsType) = info match {
58+
case ExprType(rhsType) => (Lazy, rhsType)
59+
case _ => (Method, info)
60+
}
61+
5662
synthetics +=
57-
ctx.newSymbol(ctx.owner, instanceName, Delegate | Method, info, coord = pos.span)
63+
ctx.newSymbol(ctx.owner, instanceName, Delegate | lazyOrMethod, rhsType, coord = pos.span)
5864
.entered
5965
}
6066
}
@@ -154,7 +160,7 @@ trait Deriving { this: Typer =>
154160
import tpd._
155161

156162
/** The type class instance definition with symbol `sym` */
157-
def typeclassInstance(sym: Symbol)(implicit ctx: Context): List[Type] => (List[List[tpd.Tree]] => tpd.Tree) =
163+
def typeclassDefInstance(sym: Symbol)(implicit ctx: Context): List[Type] => (List[List[tpd.Tree]] => tpd.Tree) =
158164
(tparamRefs: List[Type]) => (paramRefss: List[List[tpd.Tree]]) => {
159165
val tparams = tparamRefs.map(_.typeSymbol.asType)
160166
val params = if (paramRefss.isEmpty) Nil else paramRefss.head.map(_.symbol.asTerm)
@@ -165,20 +171,35 @@ trait Deriving { this: Typer =>
165171
case info: MethodType => info.instantiate(params.map(_.termRef))
166172
case info => info.widenExpr
167173
}
168-
def companionRef(tp: Type): TermRef = tp match {
169-
case tp @ TypeRef(prefix, _) if tp.symbol.isClass =>
170-
prefix.select(tp.symbol.companionModule).asInstanceOf[TermRef]
171-
case tp: TypeProxy =>
172-
companionRef(tp.underlying)
173-
}
174174
val resultType = instantiated(sym.info)
175175
val module = untpd.ref(companionRef(resultType)).withSpan(sym.span)
176176
val rhs = untpd.Select(module, nme.derived)
177177
typed(rhs, resultType)
178178
}
179179

180-
def syntheticDef(sym: Symbol): Tree =
181-
tpd.polyDefDef(sym.asTerm, typeclassInstance(sym)(ctx.fresh.setOwner(sym).setNewScope))
180+
def typeclassValInstance(sym: Symbol): Tree = {
181+
val rhsType = sym.info
182+
val module = untpd.ref(companionRef(rhsType)).withSpan(sym.span)
183+
val rhs = untpd.Select(module, nme.derived)
184+
val typedRhs = typed(rhs, rhsType)
185+
tpd.ValDef(sym.asTerm, typedRhs)
186+
}
187+
188+
def companionRef(tp: Type): TermRef = tp match {
189+
case tp @ TypeRef(prefix, _) if tp.symbol.isClass =>
190+
prefix.select(tp.symbol.companionModule).asInstanceOf[TermRef]
191+
case tp: TypeProxy =>
192+
companionRef(tp.underlying)
193+
}
194+
195+
def syntheticDef(sym: Symbol): Tree = {
196+
sym.info match {
197+
case _: MethodicType =>
198+
tpd.polyDefDef(sym.asTerm, typeclassDefInstance(sym)(ctx.fresh.setOwner(sym).setNewScope))
199+
case _ =>
200+
typeclassValInstance(sym)
201+
}
202+
}
182203

183204
synthetics.map(syntheticDef).toList
184205
}

tests/run/lazy-derives.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import scala.deriving._
2+
3+
object Test extends App {
4+
case class Mono(i: Int) derives Foo, Bar
5+
case class Poly[T](t: T) derives Foo, Bar, Baz
6+
7+
trait Quux[T]
8+
object Quux {
9+
given [T] as Quux[T] = new Quux[T] {}
10+
}
11+
12+
trait Foo[T]
13+
object Foo {
14+
given as Foo[Int] {}
15+
def derived[T] given (m: Mirror.Of[T]): Foo[T] = new Foo[T] {}
16+
}
17+
18+
trait Bar[T]
19+
object Bar {
20+
given as Bar[Int] {}
21+
def derived[T] given (m: Mirror.Of[T], o: Quux[T]): Bar[T] = new Bar[T] {}
22+
}
23+
24+
trait Baz[F[_]]
25+
object Baz {
26+
def derived[F[_]] given (m: Mirror { type MirroredType = F }): Baz[F] = new Baz[F] {}
27+
}
28+
29+
// Unfortunately these tests doesn't distinguish a lazy val
30+
// from a cached def
31+
assert(the[Foo[Mono]] eq the[Foo[Mono]])
32+
assert(the[Bar[Mono]] eq the[Bar[Mono]])
33+
assert(the[Baz[Poly]] eq the[Baz[Poly]])
34+
35+
// These have term arguments so should be distinct
36+
assert(the[Foo[Poly[Int]]] ne the[Foo[Poly[Int]]])
37+
assert(the[Bar[Poly[Int]]] ne the[Bar[Poly[Int]]])
38+
}

0 commit comments

Comments
 (0)