From 096e7fa5dcc207069f1079d509c0d8ea89a7695e Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Mon, 22 Jul 2019 11:15:43 +0100 Subject: [PATCH 1/2] 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. --- .../src/dotty/tools/dotc/typer/Deriving.scala | 41 ++++++++++++++----- tests/run/lazy-derives.scala | 38 +++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 tests/run/lazy-derives.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 03281d9cbca1..3ae9bc76ef14 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -53,8 +53,14 @@ trait Deriving { this: Typer => // If we set the Synthetic flag here widenDelegate will widen too far and the // derived instance will have too low a priority to be selected over a freshly // derived instance at the summoning site. + + val (lazyOrMethod, rhsType) = info match { + case ExprType(rhsType) => (Lazy, rhsType) + case _ => (Method, info) + } + synthetics += - ctx.newSymbol(ctx.owner, instanceName, Delegate | Method, info, coord = pos.span) + ctx.newSymbol(ctx.owner, instanceName, Delegate | lazyOrMethod, rhsType, coord = pos.span) .entered } } @@ -154,7 +160,7 @@ trait Deriving { this: Typer => import tpd._ /** The type class instance definition with symbol `sym` */ - def typeclassInstance(sym: Symbol)(implicit ctx: Context): List[Type] => (List[List[tpd.Tree]] => tpd.Tree) = + def typeclassDefInstance(sym: Symbol)(implicit ctx: Context): List[Type] => (List[List[tpd.Tree]] => tpd.Tree) = (tparamRefs: List[Type]) => (paramRefss: List[List[tpd.Tree]]) => { val tparams = tparamRefs.map(_.typeSymbol.asType) val params = if (paramRefss.isEmpty) Nil else paramRefss.head.map(_.symbol.asTerm) @@ -165,20 +171,35 @@ trait Deriving { this: Typer => case info: MethodType => info.instantiate(params.map(_.termRef)) case info => info.widenExpr } - def companionRef(tp: Type): TermRef = tp match { - case tp @ TypeRef(prefix, _) if tp.symbol.isClass => - prefix.select(tp.symbol.companionModule).asInstanceOf[TermRef] - case tp: TypeProxy => - companionRef(tp.underlying) - } val resultType = instantiated(sym.info) val module = untpd.ref(companionRef(resultType)).withSpan(sym.span) val rhs = untpd.Select(module, nme.derived) typed(rhs, resultType) } - def syntheticDef(sym: Symbol): Tree = - tpd.polyDefDef(sym.asTerm, typeclassInstance(sym)(ctx.fresh.setOwner(sym).setNewScope)) + def typeclassValInstance(sym: Symbol): Tree = { + val rhsType = sym.info + val module = untpd.ref(companionRef(rhsType)).withSpan(sym.span) + val rhs = untpd.Select(module, nme.derived) + val typedRhs = typed(rhs, rhsType) + tpd.ValDef(sym.asTerm, typedRhs) + } + + def companionRef(tp: Type): TermRef = tp match { + case tp @ TypeRef(prefix, _) if tp.symbol.isClass => + prefix.select(tp.symbol.companionModule).asInstanceOf[TermRef] + case tp: TypeProxy => + companionRef(tp.underlying) + } + + def syntheticDef(sym: Symbol): Tree = { + sym.info match { + case _: MethodicType => + tpd.polyDefDef(sym.asTerm, typeclassDefInstance(sym)(ctx.fresh.setOwner(sym).setNewScope)) + case _ => + typeclassValInstance(sym) + } + } synthetics.map(syntheticDef).toList } diff --git a/tests/run/lazy-derives.scala b/tests/run/lazy-derives.scala new file mode 100644 index 000000000000..6c1a42d8b19a --- /dev/null +++ b/tests/run/lazy-derives.scala @@ -0,0 +1,38 @@ +import scala.deriving._ + +object Test extends App { + case class Mono(i: Int) derives Foo, Bar + case class Poly[T](t: T) derives Foo, Bar, Baz + + trait Quux[T] + object Quux { + given [T] as Quux[T] = new Quux[T] {} + } + + trait Foo[T] + object Foo { + given as Foo[Int] {} + def derived[T] given (m: Mirror.Of[T]): Foo[T] = new Foo[T] {} + } + + trait Bar[T] + object Bar { + given as Bar[Int] {} + def derived[T] given (m: Mirror.Of[T], o: Quux[T]): Bar[T] = new Bar[T] {} + } + + trait Baz[F[_]] + object Baz { + def derived[F[_]] given (m: Mirror { type MirroredType = F }): Baz[F] = new Baz[F] {} + } + + // Unfortunately these tests doesn't distinguish a lazy val + // from a cached def + assert(the[Foo[Mono]] eq the[Foo[Mono]]) + assert(the[Bar[Mono]] eq the[Bar[Mono]]) + assert(the[Baz[Poly]] eq the[Baz[Poly]]) + + // These have term arguments so should be distinct + assert(the[Foo[Poly[Int]]] ne the[Foo[Poly[Int]]]) + assert(the[Bar[Poly[Int]]] ne the[Bar[Poly[Int]]]) +} From 42579af2dec12ea063a0def27dfbdce92bc5cfd4 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Tue, 23 Jul 2019 12:59:25 +0100 Subject: [PATCH 2/2] Use correct context for val RHS --- compiler/src/dotty/tools/dotc/typer/Deriving.scala | 9 +++++---- tests/run/lazy-derives.scala | 12 ++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 3ae9bc76ef14..6c9222f12eeb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -55,7 +55,7 @@ trait Deriving { this: Typer => // derived instance at the summoning site. val (lazyOrMethod, rhsType) = info match { - case ExprType(rhsType) => (Lazy, rhsType) + case ExprType(rhsType) => (Final | Lazy | StableRealizable, rhsType) case _ => (Method, info) } @@ -177,7 +177,7 @@ trait Deriving { this: Typer => typed(rhs, resultType) } - def typeclassValInstance(sym: Symbol): Tree = { + def typeclassValInstance(sym: Symbol)(implicit ctx: Context): Tree = { val rhsType = sym.info val module = untpd.ref(companionRef(rhsType)).withSpan(sym.span) val rhs = untpd.Select(module, nme.derived) @@ -193,11 +193,12 @@ trait Deriving { this: Typer => } def syntheticDef(sym: Symbol): Tree = { + val localCtx = ctx.fresh.setOwner(sym).setNewScope sym.info match { case _: MethodicType => - tpd.polyDefDef(sym.asTerm, typeclassDefInstance(sym)(ctx.fresh.setOwner(sym).setNewScope)) + tpd.polyDefDef(sym.asTerm, typeclassDefInstance(sym)(localCtx)) case _ => - typeclassValInstance(sym) + typeclassValInstance(sym)(localCtx) } } diff --git a/tests/run/lazy-derives.scala b/tests/run/lazy-derives.scala index 6c1a42d8b19a..23c7402a8cd2 100644 --- a/tests/run/lazy-derives.scala +++ b/tests/run/lazy-derives.scala @@ -1,8 +1,8 @@ import scala.deriving._ object Test extends App { - case class Mono(i: Int) derives Foo, Bar - case class Poly[T](t: T) derives Foo, Bar, Baz + case class Mono(i: Int) derives Foo, Bar, Inline + case class Poly[T](t: T) derives Foo, Bar, Baz, Inline trait Quux[T] object Quux { @@ -26,13 +26,21 @@ object Test extends App { def derived[F[_]] given (m: Mirror { type MirroredType = F }): Baz[F] = new Baz[F] {} } + trait Inline[T] + object Inline { + given as Inline[Int] {} + inline def derived[T] given (m: Mirror.Of[T]): Inline[T] = new Inline[T] {} + } + // Unfortunately these tests doesn't distinguish a lazy val // from a cached def assert(the[Foo[Mono]] eq the[Foo[Mono]]) assert(the[Bar[Mono]] eq the[Bar[Mono]]) assert(the[Baz[Poly]] eq the[Baz[Poly]]) + assert(the[Inline[Mono]] eq the[Inline[Mono]]) // These have term arguments so should be distinct assert(the[Foo[Poly[Int]]] ne the[Foo[Poly[Int]]]) assert(the[Bar[Poly[Int]]] ne the[Bar[Poly[Int]]]) + assert(the[Inline[Poly[Int]]] ne the[Inline[Poly[Int]]]) }