From f1400ff639f1b6e2452457c75c180bd024a8e46e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Apr 2019 14:31:34 +0200 Subject: [PATCH 01/17] Simplified test case --- tests/run/typeclass-derivation2b.scala | 512 +++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 tests/run/typeclass-derivation2b.scala diff --git a/tests/run/typeclass-derivation2b.scala b/tests/run/typeclass-derivation2b.scala new file mode 100644 index 000000000000..1e748f2c9e9d --- /dev/null +++ b/tests/run/typeclass-derivation2b.scala @@ -0,0 +1,512 @@ +import scala.collection.mutable +import scala.annotation.tailrec + +// Simulation of typeclass derivation encoding that's currently implemented. +// The real typeclass derivation is tested in typeclass-derivation3.scala. +object TypeLevel { + + /** A generic representation of a case in an ADT + * @param deriving The companion object of the ADT + * @param ordinal The ordinal value of the case in the list of the ADT's cases + * @param elems The elements of the case + */ + class Mirror(val ordinal: Int, val elems: Product) { + + /** The `n`'th element of this generic case */ + def apply(n: Int): Any = elems.productElement(n) + } + + object Mirror { + + /** A mirror of case with ordinal number `ordinal` and elements as given by `Product` */ + def apply(ordinal: Int, product: Product): Mirror = + new Mirror(ordinal, product) + + /** A mirror with elements given as an array */ + def apply(ordinal: Int, elems: Array[AnyRef]): Mirror = + apply(ordinal, new ArrayProduct(elems)) + + /** A mirror with an initial empty array of `numElems` elements, to be filled in. */ + def apply(ordinal: Int, numElems: Int): Mirror = + apply(ordinal, new Array[AnyRef](numElems)) + + /** A mirror of a case with no elements */ + def apply(ordinal: Int): Mirror = + apply(ordinal, EmptyProduct) + + /** Helper class to turn arrays into products */ + private class ArrayProduct(val elems: Array[AnyRef]) extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = elems(n) + def productArity = elems.length + override def productIterator: Iterator[Any] = elems.iterator + def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] + } + + /** Helper object */ + private object EmptyProduct extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = throw new IndexOutOfBoundsException + def productArity = 0 + } + } + + /** The shape of an ADT. + * This is eithe a product (`Case`) or a sum (`Cases`) of products. + */ + enum Shape { + + /** A sum with alternative types `Alts` */ + case Cases[Alts <: Tuple] + + /** A product type `T` with element types `Elems` */ + case Case[T, Elems <: Tuple] + } + + /** Every generic derivation starts with a typeclass instance of this type. + * It informs that type `T` has shape `S` and also implements runtime reflection on `T`. + */ + abstract class Generic[T] { + + /** The shape of the `T` */ + type Shape <: TypeLevel.Shape + + /** The case mirror corresponding to ADT instance `x` */ + def reflect(x: T): Mirror + + /** The ADT instance corresponding to given `mirror` */ + def reify(mirror: Mirror): T + } +} + +// An algebraic datatype +sealed trait Lst[+T] // derives Eq, Pickler, Show + +object Lst { + // common compiler-generated infrastructure + import TypeLevel._ + + class GenericLst[T] extends Generic[Lst[T]] { + type Shape = Shape.Cases[( + Shape.Case[Cons[T], (T, Lst[T])], + Shape.Case[Nil.type, Unit] + )] + def reflect(xs: Lst[T]): Mirror = xs match { + case xs: Cons[T] => Mirror(0, xs) + case Nil => Mirror(1) + } + def reify(c: Mirror): Lst[T] = c.ordinal match { + case 0 => Cons[T](c(0).asInstanceOf, c(1).asInstanceOf) + case 1 => Nil + } + } + + implicit def GenericLst[T]: GenericLst[T] = new GenericLst[T] + + case class Cons[T](hd: T, tl: Lst[T]) extends Lst[T] + case object Nil extends Lst[Nothing] + + // three clauses that could be generated from a `derives` clause + implicit def derived$Eq[T: Eq]: Eq[Lst[T]] = Eq.derived + //implicit def derived$Pickler[T: Pickler]: Pickler[Lst[T]] = Pickler.derived + //implicit def derived$Show[T: Show]: Show[Lst[T]] = Show.derived +} + +// A typeclass +trait Eq[T] { + def eql(x: T, y: T): Boolean +} + +object Eq { + import scala.compiletime.erasedValue + import TypeLevel._ + + inline def tryEql[T](x: T, y: T) = implicit match { + case eq: Eq[T] => eq.eql(x, y) + } + + inline def eqlElems[Elems <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryEql[elem](xm(n).asInstanceOf, ym(n).asInstanceOf) && + eqlElems[elems1](xm, ym, n + 1) + case _: Unit => + true + } + + inline def eqlCases[Alts <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean = + inline erasedValue[Alts] match { + case _: (Shape.Case[alt, elems] *: alts1) => + if (xm.ordinal == n) eqlElems[elems](xm, ym, 0) + else eqlCases[alts1](xm, ym, n + 1) + case _: Unit => + false + } + + inline def derived[T, S <: Shape](implicit ev: Generic[T]): Eq[T] = new { + def eql(x: T, y: T): Boolean = { + val xm = ev.reflect(x) + val ym = ev.reflect(y) + inline erasedValue[ev.Shape] match { + case _: Shape.Cases[alts] => + xm.ordinal == ym.ordinal && + eqlCases[alts](xm, ym, 0) + case _: Shape.Case[_, elems] => + eqlElems[elems](xm, ym, 0) + } + } + } + + implicit object IntEq extends Eq[Int] { + def eql(x: Int, y: Int) = x == y + } +} + +object Test extends App { + import TypeLevel._ + val eq = implicitly[Eq[Lst[Int]]] + val xs = Lst.Cons(11, Lst.Cons(22, Lst.Cons(33, Lst.Nil))) + val ys = Lst.Cons(11, Lst.Cons(22, Lst.Nil)) + assert(eq.eql(xs, xs)) + assert(!eq.eql(xs, ys)) + assert(!eq.eql(ys, xs)) + assert(eq.eql(ys, ys)) + + val eq2 = implicitly[Eq[Lst[Lst[Int]]]] + val xss = Lst.Cons(xs, Lst.Cons(ys, Lst.Nil)) + val yss = Lst.Cons(xs, Lst.Nil) + assert(eq2.eql(xss, xss)) + assert(!eq2.eql(xss, yss)) + assert(!eq2.eql(yss, xss)) + assert(eq2.eql(yss, yss)) +} + +/* +// A simple product type +case class Pair[T](x: T, y: T) // derives Eq, Pickler, Show + +object Pair { + // common compiler-generated infrastructure + import TypeLevel._ + + val genericClass = new GenericClass("Pair\000x\000y") + import genericClass.mirror + + private type ShapeOf[T] = Shape.Case[Pair[T], (T, T)] + + implicit def GenericPair[T]: Generic[Pair[T]] { type Shape = ShapeOf[T] } = + new Generic[Pair[T]] { + type Shape = ShapeOf[T] + def reflect(xy: Pair[T]) = + mirror(0, xy) + def reify(c: Mirror): Pair[T] = + Pair(c(0).asInstanceOf, c(1).asInstanceOf) + def common = genericClass + } + + // clauses that could be generated from a `derives` clause + implicit def derived$Eq[T: Eq]: Eq[Pair[T]] = Eq.derived + implicit def derived$Pickler[T: Pickler]: Pickler[Pair[T]] = Pickler.derived + implicit def derived$Show[T: Show]: Show[Pair[T]] = Show.derived +} + +sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pickler, Show +case class Left[L](x: L) extends Either[L, Nothing] +case class Right[R](x: R) extends Either[Nothing, R] + +object Either { + import TypeLevel._ + + val genericClass = new GenericClass("Left\000x\001Right\000x") + import genericClass.mirror + + private type ShapeOf[L, R] = Shape.Cases[( + Shape.Case[Left[L], L *: Unit], + Shape.Case[Right[R], R *: Unit] + )] + + implicit def GenericEither[L, R]: Generic[Either[L, R]] { type Shape = ShapeOf[L, R] } = + new Generic[Either[L, R]] { + type Shape = ShapeOf[L, R] + def reflect(e: Either[L, R]): Mirror = e match { + case e: Left[L] => mirror(0, e) + case e: Right[R] => mirror(1, e) + } + def reify(c: Mirror): Either[L, R] = c.ordinal match { + case 0 => Left(c(0).asInstanceOf) + case 1 => Right(c(0).asInstanceOf) + } + def common = genericClass + } + + implicit def derived$Eq[L: Eq, R: Eq]: Eq[Either[L, R]] = Eq.derived + implicit def derived$Pickler[L: Pickler, R: Pickler]: Pickler[Either[L, R]] = Pickler.derived + implicit def derived$Show[L: Show, R: Show]: Show[Either[L, R]] = Show.derived +} + +// A typeclass +trait Eq[T] { + def eql(x: T, y: T): Boolean +} + +object Eq { + import scala.compiletime.erasedValue + import TypeLevel._ + + inline def tryEql[T](x: T, y: T) = implicit match { + case eq: Eq[T] => eq.eql(x, y) + } + + inline def eqlElems[Elems <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryEql[elem](xm(n).asInstanceOf, ym(n).asInstanceOf) && + eqlElems[elems1](xm, ym, n + 1) + case _: Unit => + true + } + + inline def eqlCases[Alts <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean = + inline erasedValue[Alts] match { + case _: (Shape.Case[alt, elems] *: alts1) => + if (xm.ordinal == n) eqlElems[elems](xm, ym, 0) + else eqlCases[alts1](xm, ym, n + 1) + case _: Unit => + false + } + + inline def derived[T, S <: Shape](implicit ev: Generic[T]): Eq[T] = new { + def eql(x: T, y: T): Boolean = { + val xm = ev.reflect(x) + val ym = ev.reflect(y) + inline erasedValue[ev.Shape] match { + case _: Shape.Cases[alts] => + xm.ordinal == ym.ordinal && + eqlCases[alts](xm, ym, 0) + case _: Shape.Case[_, elems] => + eqlElems[elems](xm, ym, 0) + } + } + } + + implicit object IntEq extends Eq[Int] { + def eql(x: Int, y: Int) = x == y + } +} + +// Another typeclass +trait Pickler[T] { + def pickle(buf: mutable.ListBuffer[Int], x: T): Unit + def unpickle(buf: mutable.ListBuffer[Int]): T +} + +object Pickler { + import scala.compiletime.{erasedValue, constValue} + import TypeLevel._ + + def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1) + + inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = implicit match { + case pkl: Pickler[T] => pkl.pickle(buf, x) + } + + inline def pickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Mirror, n: Int): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryPickle[elem](buf, elems(n).asInstanceOf[elem]) + pickleElems[elems1](buf, elems, n + 1) + case _: Unit => + } + + inline def pickleCases[Alts <: Tuple](buf: mutable.ListBuffer[Int], xm: Mirror, n: Int): Unit = + inline erasedValue[Alts] match { + case _: (Shape.Case[alt, elems] *: alts1) => + if (xm.ordinal == n) pickleElems[elems](buf, xm, 0) + else pickleCases[alts1](buf, xm, n + 1) + case _: Unit => + } + + inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = implicit match { + case pkl: Pickler[T] => pkl.unpickle(buf) + } + + inline def unpickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Array[AnyRef], n: Int): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef] + unpickleElems[elems1](buf, elems, n + 1) + case _: Unit => + } + + inline def unpickleCase[T, Elems <: Tuple](gen: Generic[T], buf: mutable.ListBuffer[Int], ordinal: Int): T = { + inline val size = constValue[Tuple.Size[Elems]] + inline if (size == 0) + gen.reify(gen.common.mirror(ordinal)) + else { + val elems = new Array[Object](size) + unpickleElems[Elems](buf, elems, 0) + gen.reify(gen.common.mirror(ordinal, elems)) + } + } + + inline def unpickleCases[T, Alts <: Tuple](r: Generic[T], buf: mutable.ListBuffer[Int], ordinal: Int, n: Int): T = + inline erasedValue[Alts] match { + case _: (Shape.Case[_, elems] *: alts1) => + if (n == ordinal) unpickleCase[T, elems](r, buf, ordinal) + else unpickleCases[T, alts1](r, buf, ordinal, n + 1) + case _ => + throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ordinal") + } + + inline def derived[T, S <: Shape](implicit ev: Generic[T]): Pickler[T] = new { + def pickle(buf: mutable.ListBuffer[Int], x: T): Unit = { + val xm = ev.reflect(x) + inline erasedValue[ev.Shape] match { + case _: Shape.Cases[alts] => + buf += xm.ordinal + pickleCases[alts](buf, xm, 0) + case _: Shape.Case[_, elems] => + pickleElems[elems](buf, xm, 0) + } + } + def unpickle(buf: mutable.ListBuffer[Int]): T = inline erasedValue[ev.Shape] match { + case _: Shape.Cases[alts] => + unpickleCases[T, alts](ev, buf, nextInt(buf), 0) + case _: Shape.Case[_, elems] => + unpickleCase[T, elems](ev, buf, 0) + } + } + + implicit object IntPickler extends Pickler[Int] { + def pickle(buf: mutable.ListBuffer[Int], x: Int): Unit = buf += x + def unpickle(buf: mutable.ListBuffer[Int]): Int = nextInt(buf) + } +} + +// A third typeclass, making use of labels +trait Show[T] { + def show(x: T): String +} +object Show { + import scala.compiletime.erasedValue + import TypeLevel._ + + inline def tryShow[T](x: T): String = implicit match { + case s: Show[T] => s.show(x) + } + + inline def showElems[Elems <: Tuple](elems: Mirror, n: Int): List[String] = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + val formal = elems.elementLabel(n) + val actual = tryShow[elem](elems(n).asInstanceOf) + s"$formal = $actual" :: showElems[elems1](elems, n + 1) + case _: Unit => + Nil + } + + inline def showCases[Alts <: Tuple](xm: Mirror, n: Int): String = + inline erasedValue[Alts] match { + case _: (Shape.Case[alt, elems] *: alts1) => + if (xm.ordinal == n) showElems[elems](xm, 0).mkString(", ") + else showCases[alts1](xm, n + 1) + case _: Unit => + throw new MatchError(xm) + } + + inline def derived[T, S <: Shape](implicit ev: Generic[T]): Show[T] = new { + def show(x: T): String = { + val xm = ev.reflect(x) + val args = inline erasedValue[ev.Shape] match { + case _: Shape.Cases[alts] => + showCases[alts](xm, 0) + case _: Shape.Case[_, elems] => + showElems[elems](xm, 0).mkString(", ") + } + s"${xm.caseLabel}($args)" + } + } + + implicit object IntShow extends Show[Int] { + def show(x: Int): String = x.toString + } +} + +// Tests +object Test extends App { + import TypeLevel._ + val eq = implicitly[Eq[Lst[Int]]] + val xs = Lst.Cons(11, Lst.Cons(22, Lst.Cons(33, Lst.Nil))) + val ys = Lst.Cons(11, Lst.Cons(22, Lst.Nil)) + assert(eq.eql(xs, xs)) + assert(!eq.eql(xs, ys)) + assert(!eq.eql(ys, xs)) + assert(eq.eql(ys, ys)) + + val eq2 = implicitly[Eq[Lst[Lst[Int]]]] + val xss = Lst.Cons(xs, Lst.Cons(ys, Lst.Nil)) + val yss = Lst.Cons(xs, Lst.Nil) + assert(eq2.eql(xss, xss)) + assert(!eq2.eql(xss, yss)) + assert(!eq2.eql(yss, xss)) + assert(eq2.eql(yss, yss)) + + val buf = new mutable.ListBuffer[Int] + val pkl = implicitly[Pickler[Lst[Int]]] + pkl.pickle(buf, xs) + println(buf) + val xs1 = pkl.unpickle(buf) + println(xs1) + assert(xs1 == xs) + assert(eq.eql(xs1, xs)) + + val pkl2 = implicitly[Pickler[Lst[Lst[Int]]]] + pkl2.pickle(buf, xss) + println(buf) + val xss1 = pkl2.unpickle(buf) + println(xss1) + assert(xss == xss1) + assert(eq2.eql(xss, xss1)) + + val p1 = Pair(1, 2) + val p2 = Pair(1, 2) + val p3 = Pair(2, 1) + val eqp = implicitly[Eq[Pair[Int]]] + assert(eqp.eql(p1, p2)) + assert(!eqp.eql(p2, p3)) + + val pklp = implicitly[Pickler[Pair[Int]]] + pklp.pickle(buf, p1) + println(buf) + val p1a = pklp.unpickle(buf) + println(p1a) + assert(p1 == p1a) + assert(eqp.eql(p1, p1a)) + + def showPrintln[T: Show](x: T): Unit = + println(implicitly[Show[T]].show(x)) + showPrintln(xs) + showPrintln(xss) + + val zs = Lst.Cons(Left(1), Lst.Cons(Right(Pair(2, 3)), Lst.Nil)) + showPrintln(zs) + + def pickle[T: Pickler](buf: mutable.ListBuffer[Int], x: T): Unit = + implicitly[Pickler[T]].pickle(buf, x) + + def unpickle[T: Pickler](buf: mutable.ListBuffer[Int]): T = + implicitly[Pickler[T]].unpickle(buf) + + def copy[T: Pickler](x: T): T = { + val buf = new mutable.ListBuffer[Int] + pickle(buf, x) + unpickle[T](buf) + } + + def eql[T: Eq](x: T, y: T) = implicitly[Eq[T]].eql(x, y) + + val zs1 = copy(zs) + showPrintln(zs1) + assert(eql(zs, zs1)) +} +*/ \ No newline at end of file From e2e56eb7708df64c0a73a8efd8d3fc204ad966cd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Apr 2019 11:34:01 +0200 Subject: [PATCH 02/17] Simplify guardOK No need to enter new bindings into a context, since all references are symbolic. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 772668f897e1..32f8db1360ec 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -902,9 +902,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { def reduceCase(cdef: CaseDef): MatchRedux = { val caseBindingsBuf = new mutable.ListBuffer[ValOrDefDef]() def guardOK(implicit ctx: Context) = cdef.guard.isEmpty || { - val guardCtx = ctx.fresh.setNewScope - caseBindingsBuf.foreach(binding => guardCtx.enter(binding.symbol)) - typer.typed(cdef.guard, defn.BooleanType)(guardCtx) match { + typer.typed(cdef.guard, defn.BooleanType) match { case ConstantValue(true) => true case _ => false } From 0917b5678de4010d03083014221019e1267c8937 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Apr 2019 12:13:49 +0200 Subject: [PATCH 03/17] Introduce staging printer Avoids clutter in traces if staging is not relevant --- compiler/src/dotty/tools/dotc/config/Printers.scala | 1 + .../src/dotty/tools/dotc/transform/TreeMapWithStages.scala | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 481f9f1b68a9..f07dfe9a07a3 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -34,6 +34,7 @@ object Printers { val quotePickling: Printer = noPrinter val plugins: Printer = noPrinter val simplify: Printer = noPrinter + val staging: Printer = noPrinter val subtyping: Printer = noPrinter val tailrec: Printer = noPrinter val transforms: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index c1f98a5a42c5..e6816776a491 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -3,6 +3,7 @@ package transform import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{TreeMapWithImplicits, TreeTypeMap, tpd, untpd} +import dotty.tools.dotc.config.Printers.staging import dotty.tools.dotc.core.Constants._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ @@ -71,7 +72,7 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap override def transform(tree: Tree)(implicit ctx: Context): Tree = { if (tree.source != ctx.source && tree.source.exists) transform(tree)(ctx.withSource(tree.source)) - else reporting.trace(i"StagingTransformer.transform $tree at $level", show = true) { + else reporting.trace(i"StagingTransformer.transform $tree at $level", staging, show = true) { def mapOverTree(lastEntered: List[Symbol]) = try super.transform(tree) finally From b9399336fa479b09a3d7e9da9baabb1b77b11cdd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Apr 2019 12:20:13 +0200 Subject: [PATCH 04/17] Improve case reduction in inline matches - Don't destructively update the symbol of a case binding. This does not work reliably as the old info may flow into cached types. - Instead, create new case binding symbols and substitute old for new. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 91 ++++++++++--------- tests/{ => pending}/pos/implicit-match.scala | 1 + 2 files changed, 50 insertions(+), 42 deletions(-) rename tests/{ => pending}/pos/implicit-match.scala (93%) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 32f8db1360ec..4afdc6add08d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -730,12 +730,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val gadtSyms = typer.gadtSyms(scrutType) /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add - * bindings for variables bound in this pattern to `bindingsBuf`. + * bindings for variables bound in this pattern to `caseBindingMap`. */ def reducePattern( - bindingsBuf: mutable.ListBuffer[ValOrDefDef], - fromBuf: mutable.ListBuffer[TypeSymbol], - toBuf: mutable.ListBuffer[TypeSymbol], + caseBindingMap: mutable.ListBuffer[(Symbol, MemberDef)], scrut: TermRef, pat: Tree )(implicit ctx: Context): Boolean = { @@ -743,14 +741,20 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** Create a binding of a pattern bound variable with matching part of * scrutinee as RHS and type that corresponds to RHS. */ - def newBinding(sym: TermSymbol, rhs: Tree): Unit = { - sym.info = rhs.tpe.widenTermRefExpr - bindingsBuf += ValDef(sym, constToLiteral(rhs)).withSpan(sym.span) + def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { + val copied = sym.copy(info = rhs.tpe.widenTermRefExpr, coord = sym.coord).asTerm + caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) + } + + def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { + val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType + caseBindingMap += ((sym, TypeDef(copied))) } def searchImplicit(sym: TermSymbol, tpt: Tree) = { val evTyper = new Typer - val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)(ctx.fresh.setTyper(evTyper)) + val evCtx = ctx.fresh.setTyper(evTyper) + val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)(evCtx) evidence.tpe match { case fail: Implicits.AmbiguousImplicits => ctx.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.sourcePos) @@ -758,7 +762,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case fail: Implicits.SearchFailureType => false case _ => - newBinding(sym, evidence) + //inliner.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") + newTermBinding(sym, evidence) true } } @@ -808,6 +813,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { extractBindVariance(SimpleIdentityMap.Empty, tpt.tpe) } + def addTypeBindings(typeBinds: TypeBindsMap)(implicit ctx: Context): Unit = + typeBinds.foreachBinding { case (sym, shouldBeMinimized) => + newTypeBinding(sym, ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized)) + } + def registerAsGadtSyms(typeBinds: TypeBindsMap)(implicit ctx: Context): Unit = typeBinds.foreachBinding { case (sym, _) => val TypeBounds(lo, hi) = sym.info.bounds @@ -815,20 +825,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { ctx.gadt.addBound(sym, hi, isUpper = true) } - def addTypeBindings(typeBinds: TypeBindsMap)(implicit ctx: Context): Unit = - typeBinds.foreachBinding { case (sym, shouldBeMinimized) => - val copied = sym.copy(info = TypeAlias(ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized))).asType - fromBuf += sym - toBuf += copied - } - pat match { case Typed(pat1, tpt) => val typeBinds = getTypeBindsMap(pat1, tpt) registerAsGadtSyms(typeBinds) scrut <:< tpt.tpe && { addTypeBindings(typeBinds) - reducePattern(bindingsBuf, fromBuf, toBuf, scrut, pat1) + reducePattern(caseBindingMap, scrut, pat1) } case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => val typeBinds = getTypeBindsMap(tpt, tpt) @@ -838,8 +841,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { true } case pat @ Bind(name: TermName, body) => - reducePattern(bindingsBuf, fromBuf, toBuf, scrut, body) && { - if (name != nme.WILDCARD) newBinding(pat.symbol.asTerm, ref(scrut)) + reducePattern(caseBindingMap, scrut, body) && { + if (name != nme.WILDCARD) newTermBinding(pat.symbol.asTerm, ref(scrut)) true } case Ident(nme.WILDCARD) => @@ -862,8 +865,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case (Nil, Nil) => true case (pat :: pats1, selector :: selectors1) => val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenTermRefExpr).asTerm - newBinding(elem, selector) - reducePattern(bindingsBuf, fromBuf, toBuf, elem.termRef, pat) && + caseBindingMap += ((NoSymbol, ValDef(elem, constToLiteral(selector)).withSpan(elem.span))) + reducePattern(caseBindingMap, elem.termRef, pat) && reduceSubPatterns(pats1, selectors1) case _ => false } @@ -890,7 +893,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { false } case Inlined(EmptyTree, Nil, ipat) => - reducePattern(bindingsBuf, fromBuf, toBuf, scrut, ipat) + reducePattern(caseBindingMap, scrut, ipat) case _ => false } } @@ -900,30 +903,34 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) def reduceCase(cdef: CaseDef): MatchRedux = { - val caseBindingsBuf = new mutable.ListBuffer[ValOrDefDef]() - def guardOK(implicit ctx: Context) = cdef.guard.isEmpty || { - typer.typed(cdef.guard, defn.BooleanType) match { - case ConstantValue(true) => true - case _ => false + val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() + + def substBindings( + bindings: List[(Symbol, MemberDef)], + bbuf: mutable.ListBuffer[MemberDef], + from: List[Symbol], to: List[Symbol]): (List[MemberDef], List[Symbol], List[Symbol]) = + bindings match { + case (sym, binding) :: rest => + bbuf += binding.subst(from, to).asInstanceOf[MemberDef] + if (sym.exists) substBindings(rest, bbuf, sym :: from, binding.symbol :: to) + else substBindings(rest, bbuf, from, to) + case Nil => (bbuf.toList, from, to) } - } - if (!isImplicit) caseBindingsBuf += scrutineeBinding + + if (!isImplicit) caseBindingMap += ((NoSymbol, scrutineeBinding)) val gadtCtx = typer.gadtContext(gadtSyms).addMode(Mode.GADTflexible) - val fromBuf = mutable.ListBuffer.empty[TypeSymbol] - val toBuf = mutable.ListBuffer.empty[TypeSymbol] - if (reducePattern(caseBindingsBuf, fromBuf, toBuf, scrutineeSym.termRef, cdef.pat)(gadtCtx) && guardOK) { - val caseBindings = caseBindingsBuf.toList - val from = fromBuf.toList - val to = toBuf.toList - if (from.isEmpty) Some((caseBindings, cdef.body)) - else { - val Block(stats, expr) = tpd.Block(caseBindings, cdef.body).subst(from, to) - val typeDefs = to.collect { case sym if sym.name != tpnme.WILDCARD => tpd.TypeDef(sym).withSpan(sym.span) } - Some((typeDefs ::: stats.asInstanceOf[List[MemberDef]], expr)) + if (reducePattern(caseBindingMap, scrutineeSym.termRef, cdef.pat)(gadtCtx)) { + val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) + val guardOK = cdef.guard.isEmpty || { + typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { + case ConstantValue(true) => true + case _ => false + } } + if (guardOK) Some((caseBindings, cdef.body.subst(from, to))) + else None } - else - None + else None } def recur(cases: List[CaseDef]): MatchRedux = cases match { diff --git a/tests/pos/implicit-match.scala b/tests/pending/pos/implicit-match.scala similarity index 93% rename from tests/pos/implicit-match.scala rename to tests/pending/pos/implicit-match.scala index b3789a17d1c8..d0e5b515b90a 100644 --- a/tests/pos/implicit-match.scala +++ b/tests/pending/pos/implicit-match.scala @@ -1,3 +1,4 @@ +// Implicit matches that bind parameters don't work yet. object `implicit-match` { object invariant { case class Box[T](value: T) From b0b238338a8ea02277903b162ac21dbcb167e590 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Apr 2019 18:34:55 +0200 Subject: [PATCH 05/17] Fix owner of newSym Need to take current context instead of global Inliner context. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 2 +- tests/run/typeclass-derivation2b.scala | 92 ++++++++++++++++++- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 4afdc6add08d..9588913dba32 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -237,7 +237,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A buffer for bindings that define proxies for actual arguments */ private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = + private def newSym(name: Name, flags: FlagSet, info: Type)(implicit ctx: Context): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.span) /** A binding for the parameter of an inline method. This is a `val` def for diff --git a/tests/run/typeclass-derivation2b.scala b/tests/run/typeclass-derivation2b.scala index 1e748f2c9e9d..5a21d379b38c 100644 --- a/tests/run/typeclass-derivation2b.scala +++ b/tests/run/typeclass-derivation2b.scala @@ -16,6 +16,12 @@ object TypeLevel { def apply(n: Int): Any = elems.productElement(n) } + object EmptyProduct extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = throw new IndexOutOfBoundsException + def productArity = 0 + } + object Mirror { /** A mirror of case with ordinal number `ordinal` and elements as given by `Product` */ @@ -67,16 +73,30 @@ object TypeLevel { * It informs that type `T` has shape `S` and also implements runtime reflection on `T`. */ abstract class Generic[T] { - /** The shape of the `T` */ type Shape <: TypeLevel.Shape + /** The case mirror corresponding to ADT instance `x` */ def reflect(x: T): Mirror /** The ADT instance corresponding to given `mirror` */ def reify(mirror: Mirror): T } + + abstract class Generic2[T] { + type Shape <: Tuple + } + + abstract class GenericSum[S] extends Generic2[S] { + def ordinal(x: S): Int + def alternative(n: Int): GenericProduct[_ <: S] = ??? + } + + abstract class GenericProduct[P] extends Generic2[P] { + def toProduct(x: P): Product + def fromProduct(p: Product): P + } } // An algebraic datatype @@ -101,10 +121,41 @@ object Lst { } } + class Generic2Lst[T] extends GenericSum[Lst[T]] { + override type Shape = (Cons[T], Nil.type) + def ordinal(x: Lst[T]) = x match { + case x: Cons[_] => 0 + case Nil => 1 + } + inline override def alternative(inline n: Int) <: GenericProduct[_ <: Lst[T]] = + inline n match { + case 0 => Cons.GenericCons[T] + case 1 => Nil.GenericNil + } + } + implicit def GenericLst[T]: GenericLst[T] = new GenericLst[T] + implicit def Generic2Lst[T]: Generic2Lst[T] = new Generic2Lst[T] case class Cons[T](hd: T, tl: Lst[T]) extends Lst[T] - case object Nil extends Lst[Nothing] + + object Cons { + class GenericCons[T] extends GenericProduct[Cons[T]] { + type Shape = (T, Lst[T]) + def toProduct(x: Cons[T]): Product = x + def fromProduct(p: Product): Cons[T] = + Cons(p.productElement(0).asInstanceOf, p.productElement(1).asInstanceOf) + } + implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] + } + case object Nil extends Lst[Nothing] { + class GenericNil extends GenericProduct[Nil.type] { + type Shape = Unit + def toProduct(x: Nil.type): Product = EmptyProduct + def fromProduct(p: Product): Nil.type = Nil + } + implicit def GenericNil: GenericNil = new GenericNil + } // three clauses that could be generated from a `derives` clause implicit def derived$Eq[T: Eq]: Eq[Lst[T]] = Eq.derived @@ -143,7 +194,42 @@ object Eq { false } - inline def derived[T, S <: Shape](implicit ev: Generic[T]): Eq[T] = new { + inline def eqlProducts[Elems <: Tuple](x: Product, y: Product, n: Int): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryEql[elem](x.productElement(n).asInstanceOf, y.productElement(n).asInstanceOf) && + eqlProducts[elems1](x, y, n + 1) + case _: Unit => + true + } + + inline def eqlAlts[T, Alts <: Tuple](x: T, y: T, genSum: GenericSum[T], ord: Int, inline n: Int): Boolean = + inline erasedValue[Alts] match { + case _: (alt *: alts1) => + if (ord == n) + inline genSum.alternative(n) match { + case genProd => eqlProducts[genProd.Shape]( + genProd.toProduct(x.asInstanceOf), + genProd.toProduct(y.asInstanceOf), 0) + } + else eqlAlts[T, alts1](x, y, genSum, ord, n + 1) + case _: Unit => + false + } + + inline def derived[T](implicit ev: Generic2[T]): Eq[T] = new Eq[T] { + def eql(x: T, y: T): Boolean = { + inline ev match { + case evv: GenericSum[T] => + val ord = evv.ordinal(x) + ord == evv.ordinal(y) && eqlAlts[T, evv.Shape](x, y, evv, ord, 0) + case evv: GenericProduct[T] => + eqlProducts[evv.Shape](evv.toProduct(x), evv.toProduct(y), 0) + } + } + } + + inline def derived2[T, S <: Shape](implicit ev: Generic[T]): Eq[T] = new { def eql(x: T, y: T): Boolean = { val xm = ev.reflect(x) val ym = ev.reflect(y) From dcddc4b3a5e25a442d0ff2a4591c871d883eebb9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 3 Apr 2019 19:01:05 +0200 Subject: [PATCH 06/17] Simplify test --- tests/run/typeclass-derivation2b.scala | 162 +++++-------------------- 1 file changed, 28 insertions(+), 134 deletions(-) diff --git a/tests/run/typeclass-derivation2b.scala b/tests/run/typeclass-derivation2b.scala index 5a21d379b38c..c6ab04686574 100644 --- a/tests/run/typeclass-derivation2b.scala +++ b/tests/run/typeclass-derivation2b.scala @@ -5,95 +5,32 @@ import scala.annotation.tailrec // The real typeclass derivation is tested in typeclass-derivation3.scala. object TypeLevel { - /** A generic representation of a case in an ADT - * @param deriving The companion object of the ADT - * @param ordinal The ordinal value of the case in the list of the ADT's cases - * @param elems The elements of the case - */ - class Mirror(val ordinal: Int, val elems: Product) { - - /** The `n`'th element of this generic case */ - def apply(n: Int): Any = elems.productElement(n) - } - object EmptyProduct extends Product { def canEqual(that: Any): Boolean = true def productElement(n: Int) = throw new IndexOutOfBoundsException def productArity = 0 } - object Mirror { - - /** A mirror of case with ordinal number `ordinal` and elements as given by `Product` */ - def apply(ordinal: Int, product: Product): Mirror = - new Mirror(ordinal, product) - - /** A mirror with elements given as an array */ - def apply(ordinal: Int, elems: Array[AnyRef]): Mirror = - apply(ordinal, new ArrayProduct(elems)) - - /** A mirror with an initial empty array of `numElems` elements, to be filled in. */ - def apply(ordinal: Int, numElems: Int): Mirror = - apply(ordinal, new Array[AnyRef](numElems)) - - /** A mirror of a case with no elements */ - def apply(ordinal: Int): Mirror = - apply(ordinal, EmptyProduct) - - /** Helper class to turn arrays into products */ - private class ArrayProduct(val elems: Array[AnyRef]) extends Product { - def canEqual(that: Any): Boolean = true - def productElement(n: Int) = elems(n) - def productArity = elems.length - override def productIterator: Iterator[Any] = elems.iterator - def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] - } - - /** Helper object */ - private object EmptyProduct extends Product { - def canEqual(that: Any): Boolean = true - def productElement(n: Int) = throw new IndexOutOfBoundsException - def productArity = 0 - } - } - - /** The shape of an ADT. - * This is eithe a product (`Case`) or a sum (`Cases`) of products. - */ - enum Shape { - - /** A sum with alternative types `Alts` */ - case Cases[Alts <: Tuple] - - /** A product type `T` with element types `Elems` */ - case Case[T, Elems <: Tuple] + /** Helper class to turn arrays into products */ + class ArrayProduct(val elems: Array[AnyRef]) extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = elems(n) + def productArity = elems.length + override def productIterator: Iterator[Any] = elems.iterator + def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] } - /** Every generic derivation starts with a typeclass instance of this type. - * It informs that type `T` has shape `S` and also implements runtime reflection on `T`. - */ abstract class Generic[T] { - /** The shape of the `T` */ - type Shape <: TypeLevel.Shape - - - /** The case mirror corresponding to ADT instance `x` */ - def reflect(x: T): Mirror - - /** The ADT instance corresponding to given `mirror` */ - def reify(mirror: Mirror): T - } - - abstract class Generic2[T] { type Shape <: Tuple } - abstract class GenericSum[S] extends Generic2[S] { + abstract class GenericSum[S] extends Generic[S] { def ordinal(x: S): Int def alternative(n: Int): GenericProduct[_ <: S] = ??? } - abstract class GenericProduct[P] extends Generic2[P] { + abstract class GenericProduct[P] extends Generic[P] { + type Prod = P def toProduct(x: P): Product def fromProduct(p: Product): P } @@ -106,22 +43,7 @@ object Lst { // common compiler-generated infrastructure import TypeLevel._ - class GenericLst[T] extends Generic[Lst[T]] { - type Shape = Shape.Cases[( - Shape.Case[Cons[T], (T, Lst[T])], - Shape.Case[Nil.type, Unit] - )] - def reflect(xs: Lst[T]): Mirror = xs match { - case xs: Cons[T] => Mirror(0, xs) - case Nil => Mirror(1) - } - def reify(c: Mirror): Lst[T] = c.ordinal match { - case 0 => Cons[T](c(0).asInstanceOf, c(1).asInstanceOf) - case 1 => Nil - } - } - - class Generic2Lst[T] extends GenericSum[Lst[T]] { + class GenericLst[T] extends GenericSum[Lst[T]] { override type Shape = (Cons[T], Nil.type) def ordinal(x: Lst[T]) = x match { case x: Cons[_] => 0 @@ -135,7 +57,6 @@ object Lst { } implicit def GenericLst[T]: GenericLst[T] = new GenericLst[T] - implicit def Generic2Lst[T]: Generic2Lst[T] = new Generic2Lst[T] case class Cons[T](hd: T, tl: Lst[T]) extends Lst[T] @@ -144,7 +65,8 @@ object Lst { type Shape = (T, Lst[T]) def toProduct(x: Cons[T]): Product = x def fromProduct(p: Product): Cons[T] = - Cons(p.productElement(0).asInstanceOf, p.productElement(1).asInstanceOf) + Cons(p.productElement(0).asInstanceOf[T], + p.productElement(1).asInstanceOf[Cons[T]]) } implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] } @@ -176,69 +98,41 @@ object Eq { case eq: Eq[T] => eq.eql(x, y) } - inline def eqlElems[Elems <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean = + inline def eqlElems[Elems <: Tuple](x: Product, y: Product, n: Int): Boolean = inline erasedValue[Elems] match { case _: (elem *: elems1) => - tryEql[elem](xm(n).asInstanceOf, ym(n).asInstanceOf) && - eqlElems[elems1](xm, ym, n + 1) + tryEql[elem]( + x.productElement(n).asInstanceOf[elem], + y.productElement(n).asInstanceOf[elem]) && + eqlElems[elems1](x, y, n + 1) case _: Unit => true } - inline def eqlCases[Alts <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean = - inline erasedValue[Alts] match { - case _: (Shape.Case[alt, elems] *: alts1) => - if (xm.ordinal == n) eqlElems[elems](xm, ym, 0) - else eqlCases[alts1](xm, ym, n + 1) - case _: Unit => - false - } - - inline def eqlProducts[Elems <: Tuple](x: Product, y: Product, n: Int): Boolean = - inline erasedValue[Elems] match { - case _: (elem *: elems1) => - tryEql[elem](x.productElement(n).asInstanceOf, y.productElement(n).asInstanceOf) && - eqlProducts[elems1](x, y, n + 1) - case _: Unit => - true - } - - inline def eqlAlts[T, Alts <: Tuple](x: T, y: T, genSum: GenericSum[T], ord: Int, inline n: Int): Boolean = + inline def eqlCases[T, Alts <: Tuple](x: T, y: T, genSum: GenericSum[T], ord: Int, inline n: Int): Boolean = inline erasedValue[Alts] match { case _: (alt *: alts1) => if (ord == n) inline genSum.alternative(n) match { - case genProd => eqlProducts[genProd.Shape]( - genProd.toProduct(x.asInstanceOf), - genProd.toProduct(y.asInstanceOf), 0) + case cas => + eqlElems[cas.Shape]( + cas.toProduct(x.asInstanceOf[cas.Prod]), + cas.toProduct(y.asInstanceOf[cas.Prod]), + 0) } - else eqlAlts[T, alts1](x, y, genSum, ord, n + 1) + else eqlCases[T, alts1](x, y, genSum, ord, n + 1) case _: Unit => false } - inline def derived[T](implicit ev: Generic2[T]): Eq[T] = new Eq[T] { + inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { def eql(x: T, y: T): Boolean = { inline ev match { case evv: GenericSum[T] => val ord = evv.ordinal(x) - ord == evv.ordinal(y) && eqlAlts[T, evv.Shape](x, y, evv, ord, 0) + ord == evv.ordinal(y) && eqlCases[T, evv.Shape](x, y, evv, ord, 0) case evv: GenericProduct[T] => - eqlProducts[evv.Shape](evv.toProduct(x), evv.toProduct(y), 0) - } - } - } - - inline def derived2[T, S <: Shape](implicit ev: Generic[T]): Eq[T] = new { - def eql(x: T, y: T): Boolean = { - val xm = ev.reflect(x) - val ym = ev.reflect(y) - inline erasedValue[ev.Shape] match { - case _: Shape.Cases[alts] => - xm.ordinal == ym.ordinal && - eqlCases[alts](xm, ym, 0) - case _: Shape.Case[_, elems] => - eqlElems[elems](xm, ym, 0) + eqlElems[evv.Shape](evv.toProduct(x), evv.toProduct(y), 0) } } } From 78faf055a015d3fb6cad5fdb6397560d7919ff37 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Apr 2019 15:59:19 +0200 Subject: [PATCH 07/17] Streamline dropUnusedDefs The previous treatment could go wrong by confusing `bindings` and `termBindings` in a recursive step. test case: run/typeclass-derivation2c.scala --- .../tools/dotc/typer/ErrorReporting.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 58 ++- tests/run/typeclass-derivation2c.check | 6 + tests/run/typeclass-derivation2c.scala | 370 ++++++++++++++++++ 4 files changed, 404 insertions(+), 32 deletions(-) create mode 100644 tests/run/typeclass-derivation2c.check create mode 100644 tests/run/typeclass-derivation2c.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 855247a678c6..17cb3fd66bf4 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -81,7 +81,7 @@ object ErrorReporting { if (tree.tpe.widen.exists) i"${exprStr(tree)} does not take ${kind}parameters" else { - new FatalError("").printStackTrace() + //new FatalError("").printStackTrace() //DEBUG i"undefined: $tree # ${tree.uniqueId}: ${tree.tpe.toString} at ${ctx.phase}" } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 9588913dba32..9e46148f0bdb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1056,8 +1056,29 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(implicit ctx: Context): (List[MemberDef], Tree) = { // inlining.println(i"drop unused $bindings%, % in $tree") - - def inlineTermBindings(termBindings: List[MemberDef], tree: Tree)(implicit ctx: Context): (List[MemberDef], Tree) = { + val (termBindings, typeBindings) = bindings.partition(_.symbol.isTerm) + if (typeBindings.nonEmpty) { + val typeBindingsSet = typeBindings.foldLeft[SimpleIdentitySet[Symbol]](SimpleIdentitySet.empty)(_ + _.symbol) + val inlineTypeBindings = new TreeTypeMap( + typeMap = new TypeMap() { + override def apply(tp: Type): Type = tp match { + case tr: TypeRef if tr.prefix.eq(NoPrefix) && typeBindingsSet.contains(tr.symbol) => + val TypeAlias(res) = tr.info + res + case tp => mapOver(tp) + } + }, + treeMap = { + case ident: Ident if ident.isType && typeBindingsSet.contains(ident.symbol) => + val TypeAlias(r) = ident.symbol.info + TypeTree(r).withSpan(ident.span) + case tree => tree + } + ) + val Block(termBindings1, tree1) = inlineTypeBindings(Block(termBindings, tree)) + dropUnusedDefs(termBindings1.asInstanceOf[List[ValOrDefDef]], tree1) + } + else { val refCount = newMutableSymbolMap[Int] val bindingOfSym = newMutableSymbolMap[MemberDef] @@ -1066,7 +1087,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case vdef @ ValDef(_, _, _) => isPureExpr(vdef.rhs) case _ => false } - for (binding <- termBindings if isInlineable(binding)) { + for (binding <- bindings if isInlineable(binding)) { refCount(binding.symbol) = 0 bindingOfSym(binding.symbol) = binding } @@ -1124,40 +1145,15 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } - val retained = termBindings.filterConserve(binding => retain(binding.symbol)) - if (retained `eq` termBindings) { - (termBindings, tree) + val retained = bindings.filterConserve(binding => retain(binding.symbol)) + if (retained `eq` bindings) { + (bindings, tree) } else { val expanded = inlineBindings.transform(tree) dropUnusedDefs(retained, expanded) } } - - val (termBindings, typeBindings) = bindings.partition(_.symbol.isTerm) - if (typeBindings.isEmpty) inlineTermBindings(termBindings, tree) - else { - val typeBindingsSet = typeBindings.foldLeft[SimpleIdentitySet[Symbol]](SimpleIdentitySet.empty)(_ + _.symbol) - val inlineTypeBindings = new TreeTypeMap( - typeMap = new TypeMap() { - override def apply(tp: Type): Type = tp match { - case tr: TypeRef if tr.prefix.eq(NoPrefix) && typeBindingsSet.contains(tr.symbol) => - val TypeAlias(res) = tr.info - res - case tp => mapOver(tp) - } - }, - treeMap = { - case ident: Ident if ident.isType && typeBindingsSet.contains(ident.symbol) => - val TypeAlias(r) = ident.symbol.info - TypeTree(r).withSpan(ident.span) - case tree => tree - } - ) - - val Block(termBindings1, tree1) = inlineTypeBindings(Block(termBindings, tree)) - inlineTermBindings(termBindings1.asInstanceOf[List[ValOrDefDef]], tree1) - } } private def expandMacro(body: Tree, span: Span)(implicit ctx: Context) = { diff --git a/tests/run/typeclass-derivation2c.check b/tests/run/typeclass-derivation2c.check new file mode 100644 index 000000000000..0ab528b9da36 --- /dev/null +++ b/tests/run/typeclass-derivation2c.check @@ -0,0 +1,6 @@ +ListBuffer(0, 11, 0, 22, 0, 33, 1) +Cons(11,Cons(22,Cons(33,Nil))) +ListBuffer(0, 0, 11, 0, 22, 0, 33, 1, 0, 0, 11, 0, 22, 1, 1) +Cons(Cons(11,Cons(22,Cons(33,Nil))),Cons(Cons(11,Cons(22,Nil)),Nil)) +ListBuffer(1, 2) +Pair(1,2) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala new file mode 100644 index 000000000000..2bb93b97d735 --- /dev/null +++ b/tests/run/typeclass-derivation2c.scala @@ -0,0 +1,370 @@ +import scala.collection.mutable +import scala.annotation.tailrec + +// Simulation of an alternative typeclass derivation scheme proposed in #6153 +object TypeLevel { + + object EmptyProduct extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = throw new IndexOutOfBoundsException + def productArity = 0 + } + + /** Helper class to turn arrays into products */ + class ArrayProduct(val elems: Array[AnyRef]) extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = elems(n) + def productArity = elems.length + override def productIterator: Iterator[Any] = elems.iterator + def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] + } + + abstract class Generic[T] + + abstract class GenericSum[T] extends Generic[T] { + def ordinal(x: T): Int + def numberOfCases: Int = ??? + def alternative(n: Int): GenericProduct[_ <: T] = ??? + } + + abstract class GenericProduct[T] extends Generic[T] { + type ElemTypes <: Tuple + def toProduct(x: T): Product + def fromProduct(p: Product): T + } +} + +// An algebraic datatype +sealed trait Lst[+T] // derives Eq, Pickler, Show + +object Lst { + // common compiler-generated infrastructure + import TypeLevel._ + + class GenericLst[T] extends GenericSum[Lst[T]] { + def ordinal(x: Lst[T]) = x match { + case x: Cons[_] => 0 + case Nil => 1 + } + inline override def numberOfCases = 2 + inline override def alternative(n: Int) <: GenericProduct[_ <: Lst[T]] = + inline n match { + case 0 => Cons.GenericCons[T] + case 1 => Nil.GenericNil + } + } + + implicit def GenericLst[T]: GenericLst[T] = new GenericLst[T] + + case class Cons[T](hd: T, tl: Lst[T]) extends Lst[T] + + object Cons { + def apply[T](x: T, xs: Lst[T]): Lst[T] = new Cons(x, xs) + + class GenericCons[T] extends GenericProduct[Cons[T]] { + type ElemTypes = (T, Lst[T]) + def toProduct(x: Cons[T]): Product = x + def fromProduct(p: Product): Cons[T] = + new Cons(p.productElement(0).asInstanceOf[T], + p.productElement(1).asInstanceOf[Lst[T]]) + } + implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] + } + case object Nil extends Lst[Nothing] { + class GenericNil extends GenericProduct[Nil.type] { + type ElemTypes = Unit + def toProduct(x: Nil.type): Product = EmptyProduct + def fromProduct(p: Product): Nil.type = Nil + } + implicit def GenericNil: GenericNil = new GenericNil + } + + // three clauses that could be generated from a `derives` clause + implicit def derived$Eq[T: Eq]: Eq[Lst[T]] = Eq.derived + implicit def derived$Pickler[T: Pickler]: Pickler[Lst[T]] = Pickler.derived + //implicit def derived$Show[T: Show]: Show[Lst[T]] = Show.derived +} + +// A typeclass +trait Eq[T] { + def eql(x: T, y: T): Boolean +} + +object Eq { + import scala.compiletime.erasedValue + import TypeLevel._ + + inline def tryEql[T](x: T, y: T) = implicit match { + case eq: Eq[T] => eq.eql(x, y) + } + + inline def eqlElems[Elems <: Tuple](x: Product, y: Product, n: Int): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryEql[elem]( + x.productElement(n).asInstanceOf[elem], + y.productElement(n).asInstanceOf[elem]) && + eqlElems[elems1](x, y, n + 1) + case _: Unit => + true + } + + inline def eqlCases[T](x: T, y: T, genSum: GenericSum[T], ord: Int, inline n: Int): Boolean = + inline if (n == genSum.numberOfCases) + false + else if (ord == n) + inline genSum.alternative(n) match { + case cas: GenericProduct[p] => + eqlElems[cas.ElemTypes]( + cas.toProduct(x.asInstanceOf[p]), + cas.toProduct(y.asInstanceOf[p]), + 0) + } + else eqlCases[T](x, y, genSum, ord, n + 1) + + inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { + def eql(x: T, y: T): Boolean = { + inline ev match { + case evv: GenericSum[T] => + val ord = evv.ordinal(x) + ord == evv.ordinal(y) && eqlCases[T](x, y, evv, ord, 0) + case evv: GenericProduct[T] => + eqlElems[evv.ElemTypes](evv.toProduct(x), evv.toProduct(y), 0) + } + } + } + + implicit object IntEq extends Eq[Int] { + def eql(x: Int, y: Int) = x == y + } +} + +// A simple product type +case class Pair[T](x: T, y: T) // derives Eq, Pickler, Show + +object Pair { + // common compiler-generated infrastructure + import TypeLevel._ + + class GenericPair[T] extends GenericProduct[Pair[T]] { + type ElemTypes = (T, T) + def toProduct(x: Pair[T]): Product = x + def fromProduct(p: Product): Pair[T] = + Pair(p.productElement(0).asInstanceOf, p.productElement(1).asInstanceOf) + } + implicit def GenericPair[T]: GenericPair[T] = new GenericPair[T] + + // clauses that could be generated from a `derives` clause + implicit def derived$Eq[T: Eq]: Eq[Pair[T]] = Eq.derived + implicit def derived$Pickler[T: Pickler]: Pickler[Pair[T]] = Pickler.derived + //implicit def derived$Show[T: Show]: Show[Pair[T]] = Show.derived +} + +sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pickler, Show + +object Either { + import TypeLevel._ + + class GenericEither[L, R] extends GenericSum[Either[L, R]] { + def ordinal(x: Either[L, R]) = x match { + case x: Left[L] => 0 + case x: Right[R] => 1 + } + inline override def numberOfCases = 2 + inline override def alternative(n: Int) <: GenericProduct[_ <: Either[L, R]] = + inline n match { + case 0 => Left.GenericLeft[L] + case 1 => Right.GenericRight[R] + } + } + implicit def GenericEither[L, R]: GenericEither[L, R] = new GenericEither[L, R] + + implicit def derived$Eq[L: Eq, R: Eq]: Eq[Either[L, R]] = Eq.derived + implicit def derived$Pickler[L: Pickler, R: Pickler]: Pickler[Either[L, R]] = Pickler.derived + //implicit def derived$Show[L: Show, R: Show]: Show[Either[L, R]] = Show.derived +} + +case class Left[L](elem: L) extends Either[L, Nothing] +case class Right[R](elem: R) extends Either[Nothing, R] + +object Left { + import TypeLevel._ + class GenericLeft[L] extends GenericProduct[Left[L]] { + type ElemTypes = L *: Unit + def toProduct(x: Left[L]) = x + def fromProduct(p: Product): Left[L] = Left(p.productElement(0).asInstanceOf[L]) + } + implicit def GenericLeft[L]: GenericLeft[L] = new GenericLeft[L] +} + +object Right { + import TypeLevel._ + class GenericRight[R] extends GenericProduct[Right[R]] { + type ElemTypes = R *: Unit + def toProduct(x: Right[R]) = x + def fromProduct(p: Product): Right[R] = Right(p.productElement(0).asInstanceOf[R]) + } + implicit def GenericRight[R]: GenericRight[R] = new GenericRight[R] +} + +// Another typeclass +trait Pickler[T] { + def pickle(buf: mutable.ListBuffer[Int], x: T): Unit + def unpickle(buf: mutable.ListBuffer[Int]): T +} + +object Pickler { + import scala.compiletime.{erasedValue, constValue} + import TypeLevel._ + + def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1) + + inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = implicit match { + case pkl: Pickler[T] => pkl.pickle(buf, x) + } + + inline def pickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], x: Product, n: Int): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryPickle[elem](buf, x.productElement(n).asInstanceOf[elem]) + pickleElems[elems1](buf, x, n + 1) + case _: Unit => + } + + inline def pickleCases[T](buf: mutable.ListBuffer[Int], x: T, genSum: GenericSum[T], ord: Int, inline n: Int): Unit = + inline if (n == genSum.numberOfCases) + () + else if (ord == n) + inline genSum.alternative(n) match { + case cas: GenericProduct[p] => + pickleElems[cas.ElemTypes](buf, cas.toProduct(x.asInstanceOf[p]), 0) + } + else pickleCases[T](buf, x, genSum, ord, n + 1) + + inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = implicit match { + case pkl: Pickler[T] => pkl.unpickle(buf) + } + + inline def unpickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Array[AnyRef], n: Int): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef] + unpickleElems[elems1](buf, elems, n + 1) + case _: Unit => + } + + inline def unpickleCase[T](buf: mutable.ListBuffer[Int], genProd: GenericProduct[T]): T = { + inline val size = constValue[Tuple.Size[genProd.ElemTypes]] + inline if (size == 0) + genProd.fromProduct(EmptyProduct) + else { + val elems = new Array[Object](size) + unpickleElems[genProd.ElemTypes](buf, elems, 0) + genProd.fromProduct(ArrayProduct(elems)) + } + } + + inline def unpickleCases[T](buf: mutable.ListBuffer[Int], genSum: GenericSum[T], ord: Int, n: Int): T = + inline if (n == genSum.numberOfCases) + throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ord") + else if (ord == n) + inline genSum.alternative(n) match { + case cas: GenericProduct[p] => unpickleCase(buf, cas) + } + else unpickleCases[T](buf, genSum, ord, n + 1) + + inline def derived[T](implicit ev: Generic[T]): Pickler[T] = new { + def pickle(buf: mutable.ListBuffer[Int], x: T): Unit = + inline ev match { + case ev: GenericSum[T] => + val ord = ev.ordinal(x) + buf += ord + pickleCases[T](buf, x, ev, ord, 0) + case ev: GenericProduct[p] => + pickleElems[ev.ElemTypes](buf, ev.toProduct(x.asInstanceOf[p]), 0) + } + def unpickle(buf: mutable.ListBuffer[Int]): T = + inline ev match { + case ev: GenericSum[T] => + unpickleCases[T](buf, ev, nextInt(buf), 0) + case ev: GenericProduct[T] => + unpickleCase[T](buf, ev) + } + } + + implicit object IntPickler extends Pickler[Int] { + def pickle(buf: mutable.ListBuffer[Int], x: Int): Unit = buf += x + def unpickle(buf: mutable.ListBuffer[Int]): Int = nextInt(buf) + } +} + +// Tests +object Test extends App { + import TypeLevel._ + val eq = implicitly[Eq[Lst[Int]]] + val xs = Lst.Cons(11, Lst.Cons(22, Lst.Cons(33, Lst.Nil))) + val ys = Lst.Cons(11, Lst.Cons(22, Lst.Nil)) + assert(eq.eql(xs, xs)) + assert(!eq.eql(xs, ys)) + assert(!eq.eql(ys, xs)) + assert(eq.eql(ys, ys)) + + val eq2 = implicitly[Eq[Lst[Lst[Int]]]] + val xss = Lst.Cons(xs, Lst.Cons(ys, Lst.Nil)) + val yss = Lst.Cons(xs, Lst.Nil) + assert(eq2.eql(xss, xss)) + assert(!eq2.eql(xss, yss)) + assert(!eq2.eql(yss, xss)) + assert(eq2.eql(yss, yss)) + + val buf = new mutable.ListBuffer[Int] + val pkl = implicitly[Pickler[Lst[Int]]] + pkl.pickle(buf, xs) + println(buf) + val xs1 = pkl.unpickle(buf) + println(xs1) + assert(xs1 == xs) + assert(eq.eql(xs1, xs)) + + val pkl2 = implicitly[Pickler[Lst[Lst[Int]]]] + pkl2.pickle(buf, xss) + println(buf) + val xss1 = pkl2.unpickle(buf) + println(xss1) + assert(xss == xss1) + assert(eq2.eql(xss, xss1)) + + val p1 = Pair(1, 2) + val p2 = Pair(1, 2) + val p3 = Pair(2, 1) + val eqp = implicitly[Eq[Pair[Int]]] + assert(eqp.eql(p1, p2)) + assert(!eqp.eql(p2, p3)) + + val pklp = implicitly[Pickler[Pair[Int]]] + pklp.pickle(buf, p1) + println(buf) + val p1a = pklp.unpickle(buf) + println(p1a) + assert(p1 == p1a) + assert(eqp.eql(p1, p1a)) + + val zs = Lst.Cons(Left(1), Lst.Cons(Right(Pair(2, 3)), Lst.Nil)) + + def pickle[T: Pickler](buf: mutable.ListBuffer[Int], x: T): Unit = + implicitly[Pickler[T]].pickle(buf, x) + + def unpickle[T: Pickler](buf: mutable.ListBuffer[Int]): T = + implicitly[Pickler[T]].unpickle(buf) + + def copy[T: Pickler](x: T): T = { + val buf = new mutable.ListBuffer[Int] + pickle(buf, x) + unpickle[T](buf) + } + + def eql[T: Eq](x: T, y: T) = implicitly[Eq[T]].eql(x, y) + + val zs1 = copy(zs) + assert(eql(zs, zs1)) +} From ec2995f0185238e48c62c76a38eb64607d317cb4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Apr 2019 18:08:21 +0200 Subject: [PATCH 08/17] Update pickling blacklist --- compiler/test/dotc/run-test-pickling.blacklist | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 27c555e3f628..3056610e5116 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -8,6 +8,7 @@ tuples1a.scala typeclass-derivation1.scala typeclass-derivation2.scala typeclass-derivation2a.scala +typeclass-derivation2c.scala typeclass-derivation3.scala derive-generic.scala deriving-interesting-prefixes.scala From d16dc96d989209bf1fb2772fe245c1edac92f25d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Apr 2019 20:42:08 +0200 Subject: [PATCH 09/17] Add typelevel label info to derivation strawman --- tests/run/typeclass-derivation2c.check | 4 ++ tests/run/typeclass-derivation2c.scala | 83 +++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/tests/run/typeclass-derivation2c.check b/tests/run/typeclass-derivation2c.check index 0ab528b9da36..fd3681599ffc 100644 --- a/tests/run/typeclass-derivation2c.check +++ b/tests/run/typeclass-derivation2c.check @@ -4,3 +4,7 @@ ListBuffer(0, 0, 11, 0, 22, 0, 33, 1, 0, 0, 11, 0, 22, 1, 1) Cons(Cons(11,Cons(22,Cons(33,Nil))),Cons(Cons(11,Cons(22,Nil)),Nil)) ListBuffer(1, 2) Pair(1,2) +Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil()))) +Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil()))), tl = Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Nil())), tl = Nil())) +Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil())) +Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil())) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index 2bb93b97d735..fe51a0145298 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -29,6 +29,8 @@ object TypeLevel { abstract class GenericProduct[T] extends Generic[T] { type ElemTypes <: Tuple + type CaseLabel + type ElemLabels <: Tuple def toProduct(x: T): Product def fromProduct(p: Product): T } @@ -63,6 +65,8 @@ object Lst { class GenericCons[T] extends GenericProduct[Cons[T]] { type ElemTypes = (T, Lst[T]) + type CaseLabel = "Cons" + type ElemLabels = ("hd", "tl") def toProduct(x: Cons[T]): Product = x def fromProduct(p: Product): Cons[T] = new Cons(p.productElement(0).asInstanceOf[T], @@ -73,6 +77,8 @@ object Lst { case object Nil extends Lst[Nothing] { class GenericNil extends GenericProduct[Nil.type] { type ElemTypes = Unit + type CaseLabel = "Nil" + type ElemLabels = Unit def toProduct(x: Nil.type): Product = EmptyProduct def fromProduct(p: Product): Nil.type = Nil } @@ -82,7 +88,7 @@ object Lst { // three clauses that could be generated from a `derives` clause implicit def derived$Eq[T: Eq]: Eq[Lst[T]] = Eq.derived implicit def derived$Pickler[T: Pickler]: Pickler[Lst[T]] = Pickler.derived - //implicit def derived$Show[T: Show]: Show[Lst[T]] = Show.derived + implicit def derived$Show[T: Show]: Show[Lst[T]] = Show.derived } // A typeclass @@ -148,6 +154,8 @@ object Pair { class GenericPair[T] extends GenericProduct[Pair[T]] { type ElemTypes = (T, T) + type CaseLabel = "Pair" + type ElemLabels = ("x", "y") def toProduct(x: Pair[T]): Product = x def fromProduct(p: Product): Pair[T] = Pair(p.productElement(0).asInstanceOf, p.productElement(1).asInstanceOf) @@ -157,7 +165,7 @@ object Pair { // clauses that could be generated from a `derives` clause implicit def derived$Eq[T: Eq]: Eq[Pair[T]] = Eq.derived implicit def derived$Pickler[T: Pickler]: Pickler[Pair[T]] = Pickler.derived - //implicit def derived$Show[T: Show]: Show[Pair[T]] = Show.derived + implicit def derived$Show[T: Show]: Show[Pair[T]] = Show.derived } sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pickler, Show @@ -181,7 +189,7 @@ object Either { implicit def derived$Eq[L: Eq, R: Eq]: Eq[Either[L, R]] = Eq.derived implicit def derived$Pickler[L: Pickler, R: Pickler]: Pickler[Either[L, R]] = Pickler.derived - //implicit def derived$Show[L: Show, R: Show]: Show[Either[L, R]] = Show.derived + implicit def derived$Show[L: Show, R: Show]: Show[Either[L, R]] = Show.derived } case class Left[L](elem: L) extends Either[L, Nothing] @@ -191,6 +199,8 @@ object Left { import TypeLevel._ class GenericLeft[L] extends GenericProduct[Left[L]] { type ElemTypes = L *: Unit + type CaseLabel = "Left" + type ElemLabels = "x" *: Unit def toProduct(x: Left[L]) = x def fromProduct(p: Product): Left[L] = Left(p.productElement(0).asInstanceOf[L]) } @@ -201,6 +211,8 @@ object Right { import TypeLevel._ class GenericRight[R] extends GenericProduct[Right[R]] { type ElemTypes = R *: Unit + type CaseLabel = "Right" + type ElemLabels = "x" *: Unit def toProduct(x: Right[R]) = x def fromProduct(p: Product): Right[R] = Right(p.productElement(0).asInstanceOf[R]) } @@ -298,6 +310,63 @@ object Pickler { } } +// A third typeclass, making use of labels +trait Show[T] { + def show(x: T): String +} +object Show { + import scala.compiletime.{erasedValue, constValue} + import TypeLevel._ + + inline def tryShow[T](x: T): String = implicit match { + case s: Show[T] => s.show(x) + } + + inline def showElems[Elems <: Tuple, Labels <: Tuple](x: Product, n: Int): List[String] = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + inline erasedValue[Labels] match { + case _: (label *: labels1) => + val formal = constValue[label] + val actual = tryShow(x.productElement(n).asInstanceOf[elem]) + s"$formal = $actual" :: showElems[elems1, labels1](x, n + 1) + } + case _: Unit => + Nil + } + + inline def showCase[T](gp: GenericProduct[T], x: T): String = { + val labl = constValue[gp.CaseLabel] + showElems[gp.ElemTypes, gp.ElemLabels](gp.toProduct(x), 0) + .mkString(s"$labl(", ", ", ")") + } + + inline def showCases[T](x: T, genSum: GenericSum[T], ord: Int, n: Int): String = + inline if (n == genSum.numberOfCases) + "" + else if (ord == n) + inline genSum.alternative(n) match { + case cas: GenericProduct[p] => + showCase(cas, x.asInstanceOf[p]) + } + else showCases[T](x, genSum, ord, n + 1) + + inline def derived[T](implicit ev: Generic[T]): Show[T] = new { + def show(x: T): String = { + inline ev match { + case ev: GenericSum[T] => + showCases(x, ev, ev.ordinal(x), 0) + case ev: GenericProduct[p] => + showCase(ev, x) + } + } + } + + implicit object IntShow extends Show[Int] { + def show(x: Int): String = x.toString + } +} + // Tests object Test extends App { import TypeLevel._ @@ -349,7 +418,14 @@ object Test extends App { assert(p1 == p1a) assert(eqp.eql(p1, p1a)) + def showPrintln[T: Show](x: T): Unit = + println(implicitly[Show[T]].show(x)) + + showPrintln(xs) + showPrintln(xss) + val zs = Lst.Cons(Left(1), Lst.Cons(Right(Pair(2, 3)), Lst.Nil)) + showPrintln(zs) def pickle[T: Pickler](buf: mutable.ListBuffer[Int], x: T): Unit = implicitly[Pickler[T]].pickle(buf, x) @@ -366,5 +442,6 @@ object Test extends App { def eql[T: Eq](x: T, y: T) = implicitly[Eq[T]].eql(x, y) val zs1 = copy(zs) + showPrintln(zs1) assert(eql(zs, zs1)) } From f7d265871d32f832d3824db1d011970f230102c7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Apr 2019 23:24:31 +0200 Subject: [PATCH 10/17] Allow inline to override erased. --- .../dotty/tools/dotc/typer/RefChecks.scala | 6 +- tests/run/typeclass-derivation2c.scala | 264 ++++++++++-------- 2 files changed, 148 insertions(+), 122 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1f04a4c03b6f..4285010cdb88 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -156,8 +156,8 @@ object RefChecks { * 1.8.1 M's type is a subtype of O's type, or * 1.8.2 M is of type []S, O is of type ()T and S <: T, or * 1.8.3 M is of type ()S, O is of type []T and S <: T, or - * 1.9.1 If M or O are erased, they must both be erased - * 1.9.2 If M or O are extension methods, they must both be extension methods + * 1.9.1 If M is erased, O is erased. If O is erased, M is erased or inline. + * 1.9.2 If M or O are extension methods, they must both be extension methods. * 1.10 If M is an inline or Scala-2 macro method, O cannot be deferred unless * there's also a concrete method that M overrides. * 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro. @@ -394,7 +394,7 @@ object RefChecks { overrideError("must be declared lazy to override a lazy value") } else if (member.is(Erased) && !other.is(Erased)) { // (1.9.1) overrideError("is erased, cannot override non-erased member") - } else if (other.is(Erased) && !member.is(Erased)) { // (1.9.1) + } else if (other.is(Erased) && !member.is(Erased | Inline)) { // (1.9.1) overrideError("is not erased, cannot override erased member") } else if (member.is(Extension) && !other.is(Extension)) { // (1.9.2) overrideError("is an extension method, cannot override a normal method") diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index fe51a0145298..bd0554c1f359 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -2,7 +2,11 @@ import scala.collection.mutable import scala.annotation.tailrec // Simulation of an alternative typeclass derivation scheme proposed in #6153 -object TypeLevel { + +// -- Classes and Objects of the Derivation Framework ---------------------------------- + +/** Simulates the scala.reflect package */ +object Deriving { object EmptyProduct extends Product { def canEqual(that: Any): Boolean = true @@ -19,29 +23,47 @@ object TypeLevel { def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] } - abstract class Generic[T] + /** The Generic class hierarchy allows typelevel access to + * enums, case classes and objects, and their sealed parents. + */ + sealed abstract class Generic[T] + /** The Generic for a sum type */ abstract class GenericSum[T] extends Generic[T] { + + /** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ def ordinal(x: T): Int - def numberOfCases: Int = ??? - def alternative(n: Int): GenericProduct[_ <: T] = ??? + + /** The number of cases in the sum. + * Implemented by an inline method in concrete subclasses. + */ + erased def numberOfCases: Int = ??? + + /** The Generic representations of the sum's alternatives. + * Implemented by an inline method in concrete subclasses. + */ + erased def alternative(n: Int): GenericProduct[_ <: T] = ??? } + /** A Generic for a product type */ abstract class GenericProduct[T] extends Generic[T] { type ElemTypes <: Tuple type CaseLabel type ElemLabels <: Tuple + def toProduct(x: T): Product def fromProduct(p: Product): T } } -// An algebraic datatype +// -- Example Datatypes --------------------------------------------------------- + +// Everthing except for the base traits and their cases is supposed to be compiler-generated. + sealed trait Lst[+T] // derives Eq, Pickler, Show object Lst { - // common compiler-generated infrastructure - import TypeLevel._ + import Deriving._ class GenericLst[T] extends GenericSum[Lst[T]] { def ordinal(x: Lst[T]) = x match { @@ -74,6 +96,7 @@ object Lst { } implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] } + case object Nil extends Lst[Nothing] { class GenericNil extends GenericProduct[Nil.type] { type ElemTypes = Unit @@ -85,72 +108,18 @@ object Lst { implicit def GenericNil: GenericNil = new GenericNil } - // three clauses that could be generated from a `derives` clause + // three clauses that would be generated from a `derives` clause implicit def derived$Eq[T: Eq]: Eq[Lst[T]] = Eq.derived implicit def derived$Pickler[T: Pickler]: Pickler[Lst[T]] = Pickler.derived implicit def derived$Show[T: Show]: Show[Lst[T]] = Show.derived } -// A typeclass -trait Eq[T] { - def eql(x: T, y: T): Boolean -} - -object Eq { - import scala.compiletime.erasedValue - import TypeLevel._ - - inline def tryEql[T](x: T, y: T) = implicit match { - case eq: Eq[T] => eq.eql(x, y) - } - - inline def eqlElems[Elems <: Tuple](x: Product, y: Product, n: Int): Boolean = - inline erasedValue[Elems] match { - case _: (elem *: elems1) => - tryEql[elem]( - x.productElement(n).asInstanceOf[elem], - y.productElement(n).asInstanceOf[elem]) && - eqlElems[elems1](x, y, n + 1) - case _: Unit => - true - } - - inline def eqlCases[T](x: T, y: T, genSum: GenericSum[T], ord: Int, inline n: Int): Boolean = - inline if (n == genSum.numberOfCases) - false - else if (ord == n) - inline genSum.alternative(n) match { - case cas: GenericProduct[p] => - eqlElems[cas.ElemTypes]( - cas.toProduct(x.asInstanceOf[p]), - cas.toProduct(y.asInstanceOf[p]), - 0) - } - else eqlCases[T](x, y, genSum, ord, n + 1) - - inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { - def eql(x: T, y: T): Boolean = { - inline ev match { - case evv: GenericSum[T] => - val ord = evv.ordinal(x) - ord == evv.ordinal(y) && eqlCases[T](x, y, evv, ord, 0) - case evv: GenericProduct[T] => - eqlElems[evv.ElemTypes](evv.toProduct(x), evv.toProduct(y), 0) - } - } - } - - implicit object IntEq extends Eq[Int] { - def eql(x: Int, y: Int) = x == y - } -} - // A simple product type case class Pair[T](x: T, y: T) // derives Eq, Pickler, Show object Pair { // common compiler-generated infrastructure - import TypeLevel._ + import Deriving._ class GenericPair[T] extends GenericProduct[Pair[T]] { type ElemTypes = (T, T) @@ -168,10 +137,11 @@ object Pair { implicit def derived$Show[T: Show]: Show[Pair[T]] = Show.derived } +// Another sum type sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pickler, Show object Either { - import TypeLevel._ + import Deriving._ class GenericEither[L, R] extends GenericSum[Either[L, R]] { def ordinal(x: Either[L, R]) = x match { @@ -196,7 +166,7 @@ case class Left[L](elem: L) extends Either[L, Nothing] case class Right[R](elem: R) extends Either[Nothing, R] object Left { - import TypeLevel._ + import Deriving._ class GenericLeft[L] extends GenericProduct[Left[L]] { type ElemTypes = L *: Unit type CaseLabel = "Left" @@ -208,7 +178,7 @@ object Left { } object Right { - import TypeLevel._ + import Deriving._ class GenericRight[R] extends GenericProduct[Right[R]] { type ElemTypes = R *: Unit type CaseLabel = "Right" @@ -219,7 +189,63 @@ object Right { implicit def GenericRight[R]: GenericRight[R] = new GenericRight[R] } -// Another typeclass +// -- Type classes ------------------------------------------------------------ + +// Everything here is hand-written by the authors of the derivable typeclasses + +// Equality typeclass +trait Eq[T] { + def eql(x: T, y: T): Boolean +} + +object Eq { + import scala.compiletime.erasedValue + import Deriving._ + + inline def tryEql[T](x: T, y: T) = implicit match { + case eq: Eq[T] => eq.eql(x, y) + } + + inline def eqlElems[Elems <: Tuple](n: Int)(x: Product, y: Product): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryEql[elem]( + x.productElement(n).asInstanceOf[elem], + y.productElement(n).asInstanceOf[elem]) && + eqlElems[elems1](n + 1)(x, y) + case _: Unit => + true + } + + inline def eqlCase[T](gp: GenericProduct[T])(x: T, y: T): Boolean = + eqlElems[gp.ElemTypes](0)(gp.toProduct(x), gp.toProduct(y)) + + inline def eqlCases[T](gs: GenericSum[T], n: Int)(x: T, y: T, ord: Int): Boolean = + inline if (n == gs.numberOfCases) + false + else if (ord == n) + inline gs.alternative(n) match { + case gp: GenericProduct[p] => eqlCase[p](gp)(x.asInstanceOf[p], y.asInstanceOf[p]) + } + else eqlCases[T](gs, n + 1)(x, y, ord) + + inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { + def eql(x: T, y: T): Boolean = + inline ev match { + case gs: GenericSum[T] => + val ord = gs.ordinal(x) + ord == gs.ordinal(y) && eqlCases[T](gs, 0)(x, y, ord) + case gp: GenericProduct[T] => + eqlCase[T](gp)(x, y) + } + } + + implicit object IntEq extends Eq[Int] { + def eql(x: Int, y: Int) = x == y + } +} + +// Pickling typeclass trait Pickler[T] { def pickle(buf: mutable.ListBuffer[Int], x: T): Unit def unpickle(buf: mutable.ListBuffer[Int]): T @@ -227,7 +253,7 @@ trait Pickler[T] { object Pickler { import scala.compiletime.{erasedValue, constValue} - import TypeLevel._ + import Deriving._ def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1) @@ -235,72 +261,74 @@ object Pickler { case pkl: Pickler[T] => pkl.pickle(buf, x) } - inline def pickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], x: Product, n: Int): Unit = + inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Product): Unit = inline erasedValue[Elems] match { case _: (elem *: elems1) => tryPickle[elem](buf, x.productElement(n).asInstanceOf[elem]) - pickleElems[elems1](buf, x, n + 1) + pickleElems[elems1](n + 1)(buf, x) case _: Unit => } - inline def pickleCases[T](buf: mutable.ListBuffer[Int], x: T, genSum: GenericSum[T], ord: Int, inline n: Int): Unit = - inline if (n == genSum.numberOfCases) + inline def pickleCase[T](gp: GenericProduct[T])(buf: mutable.ListBuffer[Int], x: T): Unit = + pickleElems[gp.ElemTypes](0)(buf, gp.toProduct(x)) + + inline def pickleCases[T](gs: GenericSum[T], inline n: Int)(buf: mutable.ListBuffer[Int], x: T, ord: Int): Unit = + inline if (n == gs.numberOfCases) () else if (ord == n) - inline genSum.alternative(n) match { - case cas: GenericProduct[p] => - pickleElems[cas.ElemTypes](buf, cas.toProduct(x.asInstanceOf[p]), 0) + inline gs.alternative(n) match { + case gp: GenericProduct[p] => pickleCase(gp)(buf, x.asInstanceOf[p]) } - else pickleCases[T](buf, x, genSum, ord, n + 1) + else pickleCases[T](gs, n + 1)(buf, x, ord) inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = implicit match { case pkl: Pickler[T] => pkl.unpickle(buf) } - inline def unpickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Array[AnyRef], n: Int): Unit = + inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: Array[AnyRef]): Unit = inline erasedValue[Elems] match { case _: (elem *: elems1) => elems(n) = tryUnpickle[elem](buf).asInstanceOf[AnyRef] - unpickleElems[elems1](buf, elems, n + 1) + unpickleElems[elems1](n + 1)(buf, elems) case _: Unit => } - inline def unpickleCase[T](buf: mutable.ListBuffer[Int], genProd: GenericProduct[T]): T = { - inline val size = constValue[Tuple.Size[genProd.ElemTypes]] + inline def unpickleCase[T](gp: GenericProduct[T])(buf: mutable.ListBuffer[Int]): T = { + inline val size = constValue[Tuple.Size[gp.ElemTypes]] inline if (size == 0) - genProd.fromProduct(EmptyProduct) + gp.fromProduct(EmptyProduct) else { val elems = new Array[Object](size) - unpickleElems[genProd.ElemTypes](buf, elems, 0) - genProd.fromProduct(ArrayProduct(elems)) + unpickleElems[gp.ElemTypes](0)(buf, elems) + gp.fromProduct(ArrayProduct(elems)) } } - inline def unpickleCases[T](buf: mutable.ListBuffer[Int], genSum: GenericSum[T], ord: Int, n: Int): T = - inline if (n == genSum.numberOfCases) + inline def unpickleCases[T](gs: GenericSum[T], n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T = + inline if (n == gs.numberOfCases) throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ord") else if (ord == n) - inline genSum.alternative(n) match { - case cas: GenericProduct[p] => unpickleCase(buf, cas) + inline gs.alternative(n) match { + case gp: GenericProduct[p] => unpickleCase(gp)(buf) } - else unpickleCases[T](buf, genSum, ord, n + 1) + else unpickleCases[T](gs, n + 1)(buf, ord) inline def derived[T](implicit ev: Generic[T]): Pickler[T] = new { def pickle(buf: mutable.ListBuffer[Int], x: T): Unit = inline ev match { - case ev: GenericSum[T] => - val ord = ev.ordinal(x) + case gs: GenericSum[T] => + val ord = gs.ordinal(x) buf += ord - pickleCases[T](buf, x, ev, ord, 0) - case ev: GenericProduct[p] => - pickleElems[ev.ElemTypes](buf, ev.toProduct(x.asInstanceOf[p]), 0) + pickleCases[T](gs, 0)(buf, x, ord) + case gp: GenericProduct[p] => + pickleCase(gp)(buf, x) } def unpickle(buf: mutable.ListBuffer[Int]): T = inline ev match { - case ev: GenericSum[T] => - unpickleCases[T](buf, ev, nextInt(buf), 0) - case ev: GenericProduct[T] => - unpickleCase[T](buf, ev) + case gs: GenericSum[T] => + unpickleCases[T](gs, 0)(buf, nextInt(buf)) + case gp: GenericProduct[T] => + unpickleCase[T](gp)(buf) } } @@ -310,56 +338,53 @@ object Pickler { } } -// A third typeclass, making use of labels +// Display type class, making use of label info. trait Show[T] { def show(x: T): String } object Show { import scala.compiletime.{erasedValue, constValue} - import TypeLevel._ + import Deriving._ inline def tryShow[T](x: T): String = implicit match { case s: Show[T] => s.show(x) } - inline def showElems[Elems <: Tuple, Labels <: Tuple](x: Product, n: Int): List[String] = + inline def showElems[Elems <: Tuple, Labels <: Tuple](n: Int)(x: Product): List[String] = inline erasedValue[Elems] match { case _: (elem *: elems1) => inline erasedValue[Labels] match { case _: (label *: labels1) => val formal = constValue[label] val actual = tryShow(x.productElement(n).asInstanceOf[elem]) - s"$formal = $actual" :: showElems[elems1, labels1](x, n + 1) + s"$formal = $actual" :: showElems[elems1, labels1](n + 1)(x) } case _: Unit => Nil } - inline def showCase[T](gp: GenericProduct[T], x: T): String = { + inline def showCase[T](gp: GenericProduct[T])(x: T): String = { val labl = constValue[gp.CaseLabel] - showElems[gp.ElemTypes, gp.ElemLabels](gp.toProduct(x), 0) - .mkString(s"$labl(", ", ", ")") + showElems[gp.ElemTypes, gp.ElemLabels](0)(gp.toProduct(x)).mkString(s"$labl(", ", ", ")") } - inline def showCases[T](x: T, genSum: GenericSum[T], ord: Int, n: Int): String = - inline if (n == genSum.numberOfCases) + inline def showCases[T](gs: GenericSum[T], n: Int)(x: T, ord: Int): String = + inline if (n == gs.numberOfCases) "" else if (ord == n) - inline genSum.alternative(n) match { - case cas: GenericProduct[p] => - showCase(cas, x.asInstanceOf[p]) + inline gs.alternative(n) match { + case gp: GenericProduct[p] => showCase(gp)(x.asInstanceOf[p]) } - else showCases[T](x, genSum, ord, n + 1) + else showCases[T](gs, n + 1)(x, ord) inline def derived[T](implicit ev: Generic[T]): Show[T] = new { - def show(x: T): String = { + def show(x: T): String = inline ev match { - case ev: GenericSum[T] => - showCases(x, ev, ev.ordinal(x), 0) - case ev: GenericProduct[p] => - showCase(ev, x) + case gs: GenericSum[T] => + showCases(gs, 0)(x, gs.ordinal(x)) + case gp: GenericProduct[p] => + showCase(gp)(x) } - } } implicit object IntShow extends Show[Int] { @@ -367,9 +392,10 @@ object Show { } } -// Tests +// -- Tests ---------------------------------------------------------------------- + object Test extends App { - import TypeLevel._ + import Deriving._ val eq = implicitly[Eq[Lst[Int]]] val xs = Lst.Cons(11, Lst.Cons(22, Lst.Cons(33, Lst.Nil))) val ys = Lst.Cons(11, Lst.Cons(22, Lst.Nil)) @@ -420,7 +446,7 @@ object Test extends App { def showPrintln[T: Show](x: T): Unit = println(implicitly[Show[T]].show(x)) - + showPrintln(xs) showPrintln(xss) From 7c38fe8dacb425d508098f21afbca022e7946892 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 08:51:30 +0200 Subject: [PATCH 11/17] Special case singletons This leads to better generated code. Downside is a slight increase in the code of the deriving typeclasses. --- tests/run/typeclass-derivation2c.check | 8 +- tests/run/typeclass-derivation2c.scala | 179 +++++++++++++------------ 2 files changed, 100 insertions(+), 87 deletions(-) diff --git a/tests/run/typeclass-derivation2c.check b/tests/run/typeclass-derivation2c.check index fd3681599ffc..eee9a3d088a3 100644 --- a/tests/run/typeclass-derivation2c.check +++ b/tests/run/typeclass-derivation2c.check @@ -4,7 +4,7 @@ ListBuffer(0, 0, 11, 0, 22, 0, 33, 1, 0, 0, 11, 0, 22, 1, 1) Cons(Cons(11,Cons(22,Cons(33,Nil))),Cons(Cons(11,Cons(22,Nil)),Nil)) ListBuffer(1, 2) Pair(1,2) -Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil()))) -Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil()))), tl = Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Nil())), tl = Nil())) -Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil())) -Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil())) +Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil))) +Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil))), tl = Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Nil)), tl = Nil)) +Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil)) +Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil)) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index bd0554c1f359..d965e13e1c3e 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -27,32 +27,38 @@ object Deriving { * enums, case classes and objects, and their sealed parents. */ sealed abstract class Generic[T] + object Generic { - /** The Generic for a sum type */ - abstract class GenericSum[T] extends Generic[T] { + /** The Generic for a sum type */ + abstract class Sum[T] extends Generic[T] { - /** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ - def ordinal(x: T): Int + /** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ + def ordinal(x: T): Int - /** The number of cases in the sum. - * Implemented by an inline method in concrete subclasses. - */ - erased def numberOfCases: Int = ??? + /** The number of cases in the sum. + * Implemented by an inline method in concrete subclasses. + */ + erased def numberOfCases: Int = ??? - /** The Generic representations of the sum's alternatives. - * Implemented by an inline method in concrete subclasses. - */ - erased def alternative(n: Int): GenericProduct[_ <: T] = ??? - } + /** The Generic representations of the sum's alternatives. + * Implemented by an inline method in concrete subclasses. + */ + erased def alternative(n: Int): Generic[_ <: T] = ??? + } - /** A Generic for a product type */ - abstract class GenericProduct[T] extends Generic[T] { - type ElemTypes <: Tuple - type CaseLabel - type ElemLabels <: Tuple + /** A Generic for a product type */ + abstract class Product[T] extends Generic[T] { + type ElemTypes <: Tuple + type CaseLabel <: String + type ElemLabels <: Tuple - def toProduct(x: T): Product - def fromProduct(p: Product): T + def toProduct(x: T): scala.Product + def fromProduct(p: scala.Product): T + } + + class Singleton[T](val value: T) extends Generic[T] { + type CaseLabel <: String + } } } @@ -65,13 +71,13 @@ sealed trait Lst[+T] // derives Eq, Pickler, Show object Lst { import Deriving._ - class GenericLst[T] extends GenericSum[Lst[T]] { + class GenericLst[T] extends Generic.Sum[Lst[T]] { def ordinal(x: Lst[T]) = x match { case x: Cons[_] => 0 case Nil => 1 } inline override def numberOfCases = 2 - inline override def alternative(n: Int) <: GenericProduct[_ <: Lst[T]] = + inline override def alternative(n: Int) <: Generic[_ <: Lst[T]] = inline n match { case 0 => Cons.GenericCons[T] case 1 => Nil.GenericNil @@ -85,7 +91,7 @@ object Lst { object Cons { def apply[T](x: T, xs: Lst[T]): Lst[T] = new Cons(x, xs) - class GenericCons[T] extends GenericProduct[Cons[T]] { + class GenericCons[T] extends Generic.Product[Cons[T]] { type ElemTypes = (T, Lst[T]) type CaseLabel = "Cons" type ElemLabels = ("hd", "tl") @@ -98,12 +104,8 @@ object Lst { } case object Nil extends Lst[Nothing] { - class GenericNil extends GenericProduct[Nil.type] { - type ElemTypes = Unit + class GenericNil extends Generic.Singleton[Nil.type](Nil) { type CaseLabel = "Nil" - type ElemLabels = Unit - def toProduct(x: Nil.type): Product = EmptyProduct - def fromProduct(p: Product): Nil.type = Nil } implicit def GenericNil: GenericNil = new GenericNil } @@ -121,7 +123,7 @@ object Pair { // common compiler-generated infrastructure import Deriving._ - class GenericPair[T] extends GenericProduct[Pair[T]] { + class GenericPair[T] extends Generic.Product[Pair[T]] { type ElemTypes = (T, T) type CaseLabel = "Pair" type ElemLabels = ("x", "y") @@ -143,13 +145,13 @@ sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pic object Either { import Deriving._ - class GenericEither[L, R] extends GenericSum[Either[L, R]] { + class GenericEither[L, R] extends Generic.Sum[Either[L, R]] { def ordinal(x: Either[L, R]) = x match { case x: Left[L] => 0 case x: Right[R] => 1 } inline override def numberOfCases = 2 - inline override def alternative(n: Int) <: GenericProduct[_ <: Either[L, R]] = + inline override def alternative(n: Int) <: Generic[_ <: Either[L, R]] = inline n match { case 0 => Left.GenericLeft[L] case 1 => Right.GenericRight[R] @@ -167,7 +169,7 @@ case class Right[R](elem: R) extends Either[Nothing, R] object Left { import Deriving._ - class GenericLeft[L] extends GenericProduct[Left[L]] { + class GenericLeft[L] extends Generic.Product[Left[L]] { type ElemTypes = L *: Unit type CaseLabel = "Left" type ElemLabels = "x" *: Unit @@ -179,7 +181,7 @@ object Left { object Right { import Deriving._ - class GenericRight[R] extends GenericProduct[Right[R]] { + class GenericRight[R] extends Generic.Product[Right[R]] { type ElemTypes = R *: Unit type CaseLabel = "Right" type ElemLabels = "x" *: Unit @@ -217,26 +219,29 @@ object Eq { true } - inline def eqlCase[T](gp: GenericProduct[T])(x: T, y: T): Boolean = - eqlElems[gp.ElemTypes](0)(gp.toProduct(x), gp.toProduct(y)) + inline def eqlProduct[T](g: Generic.Product[T])(x: T, y: T): Boolean = + eqlElems[g.ElemTypes](0)(g.toProduct(x), g.toProduct(y)) - inline def eqlCases[T](gs: GenericSum[T], n: Int)(x: T, y: T, ord: Int): Boolean = - inline if (n == gs.numberOfCases) + inline def eqlCases[T](g: Generic.Sum[T], n: Int)(x: T, y: T, ord: Int): Boolean = + inline if (n == g.numberOfCases) false else if (ord == n) - inline gs.alternative(n) match { - case gp: GenericProduct[p] => eqlCase[p](gp)(x.asInstanceOf[p], y.asInstanceOf[p]) + inline g.alternative(n) match { + case g: Generic.Product[p] => eqlProduct[p](g)(x.asInstanceOf[p], y.asInstanceOf[p]) + case g: Generic.Singleton[_] => true } - else eqlCases[T](gs, n + 1)(x, y, ord) + else eqlCases[T](g, n + 1)(x, y, ord) inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { def eql(x: T, y: T): Boolean = inline ev match { - case gs: GenericSum[T] => - val ord = gs.ordinal(x) - ord == gs.ordinal(y) && eqlCases[T](gs, 0)(x, y, ord) - case gp: GenericProduct[T] => - eqlCase[T](gp)(x, y) + case g: Generic.Sum[T] => + val ord = g.ordinal(x) + ord == g.ordinal(y) && eqlCases[T](g, 0)(x, y, ord) + case g: Generic.Product[T] => + eqlProduct[T](g)(x, y) + case g: Generic.Singleton[_] => + true } } @@ -269,17 +274,18 @@ object Pickler { case _: Unit => } - inline def pickleCase[T](gp: GenericProduct[T])(buf: mutable.ListBuffer[Int], x: T): Unit = - pickleElems[gp.ElemTypes](0)(buf, gp.toProduct(x)) + inline def pickleProduct[T](g: Generic.Product[T])(buf: mutable.ListBuffer[Int], x: T): Unit = + pickleElems[g.ElemTypes](0)(buf, g.toProduct(x)) - inline def pickleCases[T](gs: GenericSum[T], inline n: Int)(buf: mutable.ListBuffer[Int], x: T, ord: Int): Unit = - inline if (n == gs.numberOfCases) + inline def pickleCases[T](g: Generic.Sum[T], inline n: Int)(buf: mutable.ListBuffer[Int], x: T, ord: Int): Unit = + inline if (n == g.numberOfCases) () else if (ord == n) - inline gs.alternative(n) match { - case gp: GenericProduct[p] => pickleCase(gp)(buf, x.asInstanceOf[p]) + inline g.alternative(n) match { + case g: Generic.Product[p] => pickleProduct(g)(buf, x.asInstanceOf[p]) + case g: Generic.Singleton[s] => } - else pickleCases[T](gs, n + 1)(buf, x, ord) + else pickleCases[T](g, n + 1)(buf, x, ord) inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = implicit match { case pkl: Pickler[T] => pkl.unpickle(buf) @@ -293,42 +299,46 @@ object Pickler { case _: Unit => } - inline def unpickleCase[T](gp: GenericProduct[T])(buf: mutable.ListBuffer[Int]): T = { - inline val size = constValue[Tuple.Size[gp.ElemTypes]] + inline def unpickleProduct[T](g: Generic.Product[T])(buf: mutable.ListBuffer[Int]): T = { + inline val size = constValue[Tuple.Size[g.ElemTypes]] inline if (size == 0) - gp.fromProduct(EmptyProduct) + g.fromProduct(EmptyProduct) else { val elems = new Array[Object](size) - unpickleElems[gp.ElemTypes](0)(buf, elems) - gp.fromProduct(ArrayProduct(elems)) + unpickleElems[g.ElemTypes](0)(buf, elems) + g.fromProduct(ArrayProduct(elems)) } } - inline def unpickleCases[T](gs: GenericSum[T], n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T = - inline if (n == gs.numberOfCases) + inline def unpickleCases[T](g: Generic.Sum[T], n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T = + inline if (n == g.numberOfCases) throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ord") else if (ord == n) - inline gs.alternative(n) match { - case gp: GenericProduct[p] => unpickleCase(gp)(buf) + inline g.alternative(n) match { + case g: Generic.Product[p] => unpickleProduct(g)(buf) + case g: Generic.Singleton[s] => g.value } - else unpickleCases[T](gs, n + 1)(buf, ord) + else unpickleCases[T](g, n + 1)(buf, ord) inline def derived[T](implicit ev: Generic[T]): Pickler[T] = new { def pickle(buf: mutable.ListBuffer[Int], x: T): Unit = inline ev match { - case gs: GenericSum[T] => - val ord = gs.ordinal(x) + case g: Generic.Sum[T] => + val ord = g.ordinal(x) buf += ord - pickleCases[T](gs, 0)(buf, x, ord) - case gp: GenericProduct[p] => - pickleCase(gp)(buf, x) + pickleCases[T](g, 0)(buf, x, ord) + case g: Generic.Product[p] => + pickleProduct(g)(buf, x) + case g: Generic.Singleton[_] => } def unpickle(buf: mutable.ListBuffer[Int]): T = inline ev match { - case gs: GenericSum[T] => - unpickleCases[T](gs, 0)(buf, nextInt(buf)) - case gp: GenericProduct[T] => - unpickleCase[T](gp)(buf) + case g: Generic.Sum[T] => + unpickleCases[T](g, 0)(buf, nextInt(buf)) + case g: Generic.Product[T] => + unpickleProduct[T](g)(buf) + case g: Generic.Singleton[s] => + constValue[s] } } @@ -363,27 +373,30 @@ object Show { Nil } - inline def showCase[T](gp: GenericProduct[T])(x: T): String = { - val labl = constValue[gp.CaseLabel] - showElems[gp.ElemTypes, gp.ElemLabels](0)(gp.toProduct(x)).mkString(s"$labl(", ", ", ")") + inline def showProduct[T](g: Generic.Product[T])(x: T): String = { + val labl = constValue[g.CaseLabel] + showElems[g.ElemTypes, g.ElemLabels](0)(g.toProduct(x)).mkString(s"$labl(", ", ", ")") } - inline def showCases[T](gs: GenericSum[T], n: Int)(x: T, ord: Int): String = - inline if (n == gs.numberOfCases) + inline def showCases[T](g: Generic.Sum[T], n: Int)(x: T, ord: Int): String = + inline if (n == g.numberOfCases) "" else if (ord == n) - inline gs.alternative(n) match { - case gp: GenericProduct[p] => showCase(gp)(x.asInstanceOf[p]) + inline g.alternative(n) match { + case g: Generic.Product[p] => showProduct(g)(x.asInstanceOf[p]) + case g: Generic.Singleton[s] => constValue[g.CaseLabel] } - else showCases[T](gs, n + 1)(x, ord) + else showCases[T](g, n + 1)(x, ord) inline def derived[T](implicit ev: Generic[T]): Show[T] = new { def show(x: T): String = inline ev match { - case gs: GenericSum[T] => - showCases(gs, 0)(x, gs.ordinal(x)) - case gp: GenericProduct[p] => - showCase(gp)(x) + case g: Generic.Sum[T] => + showCases(g, 0)(x, g.ordinal(x)) + case g: Generic.Product[p] => + showProduct(g)(x) + case g: Generic.Singleton[s] => + constValue[g.CaseLabel] } } From 20e54a320aa525b4b73531adabe9ef6e1a8a49b6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 10:00:35 +0200 Subject: [PATCH 12/17] Fix valueOf ValueOf would happily return a value for non-value types, e.g. valueOf[String]. This would crash the backend later. This commit replaces the crash with a type error. Ideally, though, this should work as expected in inlined code. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- tests/neg/i6241.scala | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i6241.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 99e6800ad66d..6d7b1da08f7b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -773,7 +773,7 @@ trait Implicits { self: Typer => success(Literal(c)) case TypeRef(_, sym) if sym == defn.UnitClass => success(Literal(Constant(()))) - case n: NamedType => + case n: TermRef => success(ref(n)) case tp => EmptyTree diff --git a/tests/neg/i6241.scala b/tests/neg/i6241.scala new file mode 100644 index 000000000000..7c37159e7f0f --- /dev/null +++ b/tests/neg/i6241.scala @@ -0,0 +1,5 @@ +object Test extends App { + inline def v[T] = valueOf[T] // error + + println(v[String]) +} \ No newline at end of file From 2243e2d5af208ee5749ab5d1f42f26bc4bb9b1f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 11:52:41 +0200 Subject: [PATCH 13/17] Eliminate toProduct in test case --- tests/run/typeclass-derivation2c.scala | 48 +++++++++++--------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index d965e13e1c3e..89fe23e058a8 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -23,6 +23,8 @@ object Deriving { def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] } + def productElement[T](x: Any, idx: Int) = x.asInstanceOf[Product].productElement(idx).asInstanceOf[T] + /** The Generic class hierarchy allows typelevel access to * enums, case classes and objects, and their sealed parents. */ @@ -52,7 +54,6 @@ object Deriving { type CaseLabel <: String type ElemLabels <: Tuple - def toProduct(x: T): scala.Product def fromProduct(p: scala.Product): T } @@ -95,10 +96,8 @@ object Lst { type ElemTypes = (T, Lst[T]) type CaseLabel = "Cons" type ElemLabels = ("hd", "tl") - def toProduct(x: Cons[T]): Product = x def fromProduct(p: Product): Cons[T] = - new Cons(p.productElement(0).asInstanceOf[T], - p.productElement(1).asInstanceOf[Lst[T]]) + new Cons(productElement[T](p, 0), productElement[Lst[T]](p, 1)) } implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] } @@ -127,9 +126,8 @@ object Pair { type ElemTypes = (T, T) type CaseLabel = "Pair" type ElemLabels = ("x", "y") - def toProduct(x: Pair[T]): Product = x def fromProduct(p: Product): Pair[T] = - Pair(p.productElement(0).asInstanceOf, p.productElement(1).asInstanceOf) + Pair(productElement[T](p, 0), productElement[T](p, 1)) } implicit def GenericPair[T]: GenericPair[T] = new GenericPair[T] @@ -173,8 +171,7 @@ object Left { type ElemTypes = L *: Unit type CaseLabel = "Left" type ElemLabels = "x" *: Unit - def toProduct(x: Left[L]) = x - def fromProduct(p: Product): Left[L] = Left(p.productElement(0).asInstanceOf[L]) + def fromProduct(p: Product): Left[L] = Left(productElement[L](p, 0)) } implicit def GenericLeft[L]: GenericLeft[L] = new GenericLeft[L] } @@ -185,8 +182,7 @@ object Right { type ElemTypes = R *: Unit type CaseLabel = "Right" type ElemLabels = "x" *: Unit - def toProduct(x: Right[R]) = x - def fromProduct(p: Product): Right[R] = Right(p.productElement(0).asInstanceOf[R]) + def fromProduct(p: Product): Right[R] = Right(productElement[R](p, 0)) } implicit def GenericRight[R]: GenericRight[R] = new GenericRight[R] } @@ -208,26 +204,24 @@ object Eq { case eq: Eq[T] => eq.eql(x, y) } - inline def eqlElems[Elems <: Tuple](n: Int)(x: Product, y: Product): Boolean = + inline def eqlElems[Elems <: Tuple](n: Int)(x: Any, y: Any): Boolean = inline erasedValue[Elems] match { case _: (elem *: elems1) => - tryEql[elem]( - x.productElement(n).asInstanceOf[elem], - y.productElement(n).asInstanceOf[elem]) && + tryEql[elem](productElement[elem](x, n), productElement[elem](y, n)) && eqlElems[elems1](n + 1)(x, y) case _: Unit => true } - inline def eqlProduct[T](g: Generic.Product[T])(x: T, y: T): Boolean = - eqlElems[g.ElemTypes](0)(g.toProduct(x), g.toProduct(y)) + inline def eqlProduct[T](g: Generic.Product[T])(x: Any, y: Any): Boolean = + eqlElems[g.ElemTypes](0)(x, y) inline def eqlCases[T](g: Generic.Sum[T], n: Int)(x: T, y: T, ord: Int): Boolean = inline if (n == g.numberOfCases) false else if (ord == n) inline g.alternative(n) match { - case g: Generic.Product[p] => eqlProduct[p](g)(x.asInstanceOf[p], y.asInstanceOf[p]) + case g: Generic.Product[p] => eqlProduct[p](g)(x, y) case g: Generic.Singleton[_] => true } else eqlCases[T](g, n + 1)(x, y, ord) @@ -266,23 +260,23 @@ object Pickler { case pkl: Pickler[T] => pkl.pickle(buf, x) } - inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Product): Unit = + inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any): Unit = inline erasedValue[Elems] match { case _: (elem *: elems1) => - tryPickle[elem](buf, x.productElement(n).asInstanceOf[elem]) + tryPickle[elem](buf, productElement[elem](x, n)) pickleElems[elems1](n + 1)(buf, x) case _: Unit => } - inline def pickleProduct[T](g: Generic.Product[T])(buf: mutable.ListBuffer[Int], x: T): Unit = - pickleElems[g.ElemTypes](0)(buf, g.toProduct(x)) + inline def pickleProduct[T](g: Generic.Product[T])(buf: mutable.ListBuffer[Int], x: Any): Unit = + pickleElems[g.ElemTypes](0)(buf, x) inline def pickleCases[T](g: Generic.Sum[T], inline n: Int)(buf: mutable.ListBuffer[Int], x: T, ord: Int): Unit = inline if (n == g.numberOfCases) () else if (ord == n) inline g.alternative(n) match { - case g: Generic.Product[p] => pickleProduct(g)(buf, x.asInstanceOf[p]) + case g: Generic.Product[p] => pickleProduct(g)(buf, x) case g: Generic.Singleton[s] => } else pickleCases[T](g, n + 1)(buf, x, ord) @@ -360,22 +354,22 @@ object Show { case s: Show[T] => s.show(x) } - inline def showElems[Elems <: Tuple, Labels <: Tuple](n: Int)(x: Product): List[String] = + inline def showElems[Elems <: Tuple, Labels <: Tuple](n: Int)(x: Any): List[String] = inline erasedValue[Elems] match { case _: (elem *: elems1) => inline erasedValue[Labels] match { case _: (label *: labels1) => val formal = constValue[label] - val actual = tryShow(x.productElement(n).asInstanceOf[elem]) + val actual = tryShow(productElement[elem](x, n)) s"$formal = $actual" :: showElems[elems1, labels1](n + 1)(x) } case _: Unit => Nil } - inline def showProduct[T](g: Generic.Product[T])(x: T): String = { + inline def showProduct[T](g: Generic.Product[T])(x: Any): String = { val labl = constValue[g.CaseLabel] - showElems[g.ElemTypes, g.ElemLabels](0)(g.toProduct(x)).mkString(s"$labl(", ", ", ")") + showElems[g.ElemTypes, g.ElemLabels](0)(x).mkString(s"$labl(", ", ", ")") } inline def showCases[T](g: Generic.Sum[T], n: Int)(x: T, ord: Int): String = @@ -383,7 +377,7 @@ object Show { "" else if (ord == n) inline g.alternative(n) match { - case g: Generic.Product[p] => showProduct(g)(x.asInstanceOf[p]) + case g: Generic.Product[p] => showProduct(g)(x) case g: Generic.Singleton[s] => constValue[g.CaseLabel] } else showCases[T](g, n + 1)(x, ord) From 4d84b4cbded82277797e0c9e0ddcc257af5d5726 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 12:00:36 +0200 Subject: [PATCH 14/17] Test case: Drop value argument of Generic.Singleton --- tests/run/typeclass-derivation2c.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index 89fe23e058a8..95cb63f6809e 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -57,7 +57,10 @@ object Deriving { def fromProduct(p: scala.Product): T } - class Singleton[T](val value: T) extends Generic[T] { + class Singleton[T] extends Generic[T] { + inline def singletonValue = implicit match { + case ev: ValueOf[T] => ev.value + } type CaseLabel <: String } } @@ -103,7 +106,7 @@ object Lst { } case object Nil extends Lst[Nothing] { - class GenericNil extends Generic.Singleton[Nil.type](Nil) { + class GenericNil extends Generic.Singleton[Nil.type] { type CaseLabel = "Nil" } implicit def GenericNil: GenericNil = new GenericNil @@ -310,7 +313,7 @@ object Pickler { else if (ord == n) inline g.alternative(n) match { case g: Generic.Product[p] => unpickleProduct(g)(buf) - case g: Generic.Singleton[s] => g.value + case g: Generic.Singleton[s] => g.singletonValue } else unpickleCases[T](g, n + 1)(buf, ord) From 97c546305bfeda4b017a341e32ddc00680ea0cb1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 12:04:11 +0200 Subject: [PATCH 15/17] test case: Make case objects be their own generic instances --- tests/run/typeclass-derivation2c.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index 95cb63f6809e..6a7f6697ff4b 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -57,7 +57,7 @@ object Deriving { def fromProduct(p: scala.Product): T } - class Singleton[T] extends Generic[T] { + trait Singleton[T] extends Generic[T] { inline def singletonValue = implicit match { case ev: ValueOf[T] => ev.value } @@ -105,11 +105,9 @@ object Lst { implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] } - case object Nil extends Lst[Nothing] { - class GenericNil extends Generic.Singleton[Nil.type] { - type CaseLabel = "Nil" - } - implicit def GenericNil: GenericNil = new GenericNil + case object Nil extends Lst[Nothing] with Generic.Singleton[Nil.type] { + type CaseLabel = "Nil" + implicit def GenericNil: Nil.type = this } // three clauses that would be generated from a `derives` clause From 219318ea0bd27a49761244128b4c0e5720678a42 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 14:07:09 +0200 Subject: [PATCH 16/17] Test case: Don't use separate classes for Generic.Products Use the companion objects instead. --- tests/run/typeclass-derivation2c.scala | 58 ++++++++++---------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index 6a7f6697ff4b..2a1181d16ca0 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -65,6 +65,7 @@ object Deriving { } } } +import Deriving._ // -- Example Datatypes --------------------------------------------------------- @@ -73,7 +74,6 @@ object Deriving { sealed trait Lst[+T] // derives Eq, Pickler, Show object Lst { - import Deriving._ class GenericLst[T] extends Generic.Sum[Lst[T]] { def ordinal(x: Lst[T]) = x match { @@ -92,17 +92,17 @@ object Lst { case class Cons[T](hd: T, tl: Lst[T]) extends Lst[T] - object Cons { + object Cons extends Generic.Product[Cons[_]] { def apply[T](x: T, xs: Lst[T]): Lst[T] = new Cons(x, xs) - class GenericCons[T] extends Generic.Product[Cons[T]] { + def fromProduct(p: Product): Cons[_] = + new Cons(productElement[Any](p, 0), productElement[Lst[Any]](p, 1)) + + implicit def GenericCons[T]: Generic.Product[Cons[T]] { type ElemTypes = (T, Lst[T]) type CaseLabel = "Cons" type ElemLabels = ("hd", "tl") - def fromProduct(p: Product): Cons[T] = - new Cons(productElement[T](p, 0), productElement[Lst[T]](p, 1)) - } - implicit def GenericCons[T]: GenericCons[T] = new GenericCons[T] + } = this.asInstanceOf } case object Nil extends Lst[Nothing] with Generic.Singleton[Nil.type] { @@ -119,18 +119,16 @@ object Lst { // A simple product type case class Pair[T](x: T, y: T) // derives Eq, Pickler, Show -object Pair { - // common compiler-generated infrastructure - import Deriving._ +object Pair extends Generic.Product[Pair[_]] { - class GenericPair[T] extends Generic.Product[Pair[T]] { + def fromProduct(p: Product): Pair[_] = + Pair(productElement[Any](p, 0), productElement[Any](p, 1)) + + implicit def GenericPair[T]: Generic.Product[Pair[T]] { type ElemTypes = (T, T) type CaseLabel = "Pair" type ElemLabels = ("x", "y") - def fromProduct(p: Product): Pair[T] = - Pair(productElement[T](p, 0), productElement[T](p, 1)) - } - implicit def GenericPair[T]: GenericPair[T] = new GenericPair[T] + } = this.asInstanceOf // clauses that could be generated from a `derives` clause implicit def derived$Eq[T: Eq]: Eq[Pair[T]] = Eq.derived @@ -142,12 +140,10 @@ object Pair { sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pickler, Show object Either { - import Deriving._ - class GenericEither[L, R] extends Generic.Sum[Either[L, R]] { def ordinal(x: Either[L, R]) = x match { - case x: Left[L] => 0 - case x: Right[R] => 1 + case x: Left[_] => 0 + case x: Right[_] => 1 } inline override def numberOfCases = 2 inline override def alternative(n: Int) <: Generic[_ <: Either[L, R]] = @@ -166,26 +162,22 @@ object Either { case class Left[L](elem: L) extends Either[L, Nothing] case class Right[R](elem: R) extends Either[Nothing, R] -object Left { - import Deriving._ - class GenericLeft[L] extends Generic.Product[Left[L]] { +object Left extends Generic.Product[Left[_]] { + def fromProduct(p: Product): Left[_] = Left(productElement[Any](p, 0)) + implicit def GenericLeft[L]: Generic.Product[Left[L]] { type ElemTypes = L *: Unit type CaseLabel = "Left" type ElemLabels = "x" *: Unit - def fromProduct(p: Product): Left[L] = Left(productElement[L](p, 0)) - } - implicit def GenericLeft[L]: GenericLeft[L] = new GenericLeft[L] + } = this.asInstanceOf } -object Right { - import Deriving._ - class GenericRight[R] extends Generic.Product[Right[R]] { +object Right extends Generic.Product[Right[_]] { + def fromProduct(p: Product): Right[_] = Right(productElement[Any](p, 0)) + implicit def GenericRight[R]: Generic.Product[Right[R]] { type ElemTypes = R *: Unit type CaseLabel = "Right" type ElemLabels = "x" *: Unit - def fromProduct(p: Product): Right[R] = Right(productElement[R](p, 0)) - } - implicit def GenericRight[R]: GenericRight[R] = new GenericRight[R] + } = this.asInstanceOf } // -- Type classes ------------------------------------------------------------ @@ -199,7 +191,6 @@ trait Eq[T] { object Eq { import scala.compiletime.erasedValue - import Deriving._ inline def tryEql[T](x: T, y: T) = implicit match { case eq: Eq[T] => eq.eql(x, y) @@ -253,7 +244,6 @@ trait Pickler[T] { object Pickler { import scala.compiletime.{erasedValue, constValue} - import Deriving._ def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1) @@ -349,7 +339,6 @@ trait Show[T] { } object Show { import scala.compiletime.{erasedValue, constValue} - import Deriving._ inline def tryShow[T](x: T): String = implicit match { case s: Show[T] => s.show(x) @@ -403,7 +392,6 @@ object Show { // -- Tests ---------------------------------------------------------------------- object Test extends App { - import Deriving._ val eq = implicitly[Eq[Lst[Int]]] val xs = Lst.Cons(11, Lst.Cons(22, Lst.Cons(33, Lst.Nil))) val ys = Lst.Cons(11, Lst.Cons(22, Lst.Nil)) From 1929522d7bd1b8ebfdecb8e8571d4c4754ba5ea8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 5 Apr 2019 15:09:37 +0200 Subject: [PATCH 17/17] Flesh out docs --- tests/run/typeclass-derivation2c.scala | 70 ++++++++++++++++---------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/tests/run/typeclass-derivation2c.scala b/tests/run/typeclass-derivation2c.scala index 2a1181d16ca0..eb4db03901df 100644 --- a/tests/run/typeclass-derivation2c.scala +++ b/tests/run/typeclass-derivation2c.scala @@ -5,26 +5,9 @@ import scala.annotation.tailrec // -- Classes and Objects of the Derivation Framework ---------------------------------- -/** Simulates the scala.reflect package */ +/** Core classes. In the current implementation these are in the scala.reflect package */ object Deriving { - object EmptyProduct extends Product { - def canEqual(that: Any): Boolean = true - def productElement(n: Int) = throw new IndexOutOfBoundsException - def productArity = 0 - } - - /** Helper class to turn arrays into products */ - class ArrayProduct(val elems: Array[AnyRef]) extends Product { - def canEqual(that: Any): Boolean = true - def productElement(n: Int) = elems(n) - def productArity = elems.length - override def productIterator: Iterator[Any] = elems.iterator - def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] - } - - def productElement[T](x: Any, idx: Int) = x.asInstanceOf[Product].productElement(idx).asInstanceOf[T] - /** The Generic class hierarchy allows typelevel access to * enums, case classes and objects, and their sealed parents. */ @@ -48,22 +31,47 @@ object Deriving { erased def alternative(n: Int): Generic[_ <: T] = ??? } - /** A Generic for a product type */ + /** The Generic for a product type */ abstract class Product[T] extends Generic[T] { + + /** The types of the elements */ type ElemTypes <: Tuple + + /** The name of the whole product type */ type CaseLabel <: String + + /** The names of the product elements */ type ElemLabels <: Tuple + /** Create a new instance of type `T` with elements taken from product `p`. */ def fromProduct(p: scala.Product): T } + /** The Generic for a singleton */ trait Singleton[T] extends Generic[T] { + + /** The name of the singleton */ + type CaseLabel <: String + + /** The represented value */ inline def singletonValue = implicit match { case ev: ValueOf[T] => ev.value } - type CaseLabel <: String } } + + /** Helper class to turn arrays into products */ + class ArrayProduct(val elems: Array[AnyRef]) extends Product { + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = elems(n) + def productArity = elems.length + override def productIterator: Iterator[Any] = elems.iterator + def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] + } + + /** Helper method to select a product element */ + def productElement[T](x: Any, idx: Int) = + x.asInstanceOf[Product].productElement(idx).asInstanceOf[T] } import Deriving._ @@ -183,6 +191,18 @@ object Right extends Generic.Product[Right[_]] { // -- Type classes ------------------------------------------------------------ // Everything here is hand-written by the authors of the derivable typeclasses +// The same schema is used throughout. +// +// - A typeclass implements an inline `derived` method, given a `Generic` instance. +// - Each implemented typeclass operation `xyz` calls 4 inline helper methods: +// 1. `xyzCases` for sums, +// 2. `xyzProduct` for products, +// 3. `xyzElems` stepping through the elements of a product, +// 4. `tryXyz` for searching the implicit to handles a single element. +// - The first three methods have two parameter lists. The first parameter +// list contains inline parameters that guide the code generation, whereas +// the second parameter list contains parameters that show up in the +// generated code. (This is done just to make things clearer). // Equality typeclass trait Eq[T] { @@ -286,13 +306,9 @@ object Pickler { inline def unpickleProduct[T](g: Generic.Product[T])(buf: mutable.ListBuffer[Int]): T = { inline val size = constValue[Tuple.Size[g.ElemTypes]] - inline if (size == 0) - g.fromProduct(EmptyProduct) - else { - val elems = new Array[Object](size) - unpickleElems[g.ElemTypes](0)(buf, elems) - g.fromProduct(ArrayProduct(elems)) - } + val elems = new Array[Object](size) + unpickleElems[g.ElemTypes](0)(buf, elems) + g.fromProduct(ArrayProduct(elems)) } inline def unpickleCases[T](g: Generic.Sum[T], n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T =