Skip to content

Non-parametric derived instances now lazy vals #6905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Deriving.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (Final | Lazy | StableRealizable, 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
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -165,20 +171,36 @@ 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)(implicit ctx: Context): 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 = {
val localCtx = ctx.fresh.setOwner(sym).setNewScope
sym.info match {
case _: MethodicType =>
tpd.polyDefDef(sym.asTerm, typeclassDefInstance(sym)(localCtx))
case _ =>
typeclassValInstance(sym)(localCtx)
}
}

synthetics.map(syntheticDef).toList
}
Expand Down
46 changes: 46 additions & 0 deletions tests/run/lazy-derives.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import scala.deriving._

object Test extends App {
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 {
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] {}
}

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]]])
}