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 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/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/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 772668f897e1..9e46148f0bdb 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 @@ -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,32 +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 || { - val guardCtx = ctx.fresh.setNewScope - caseBindingsBuf.foreach(binding => guardCtx.enter(binding.symbol)) - typer.typed(cdef.guard, defn.BooleanType)(guardCtx) 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 { @@ -1051,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] @@ -1061,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 } @@ -1119,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/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/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 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 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) diff --git a/tests/run/typeclass-derivation2b.scala b/tests/run/typeclass-derivation2b.scala new file mode 100644 index 000000000000..c6ab04686574 --- /dev/null +++ b/tests/run/typeclass-derivation2b.scala @@ -0,0 +1,492 @@ +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 { + + 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] { + type Shape <: Tuple + } + + abstract class GenericSum[S] extends Generic[S] { + def ordinal(x: S): Int + def alternative(n: Int): GenericProduct[_ <: S] = ??? + } + + abstract class GenericProduct[P] extends Generic[P] { + type Prod = P + def toProduct(x: P): Product + def fromProduct(p: Product): P + } +} + +// 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]] { + 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] + + case class Cons[T](hd: T, tl: Lst[T]) extends Lst[T] + + 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[T], + p.productElement(1).asInstanceOf[Cons[T]]) + } + 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 + //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, 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 cas => + eqlElems[cas.Shape]( + cas.toProduct(x.asInstanceOf[cas.Prod]), + cas.toProduct(y.asInstanceOf[cas.Prod]), + 0) + } + else eqlCases[T, alts1](x, y, genSum, ord, n + 1) + case _: Unit => + false + } + + 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, evv.Shape](x, y, evv, ord, 0) + case evv: GenericProduct[T] => + eqlElems[evv.Shape](evv.toProduct(x), evv.toProduct(y), 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 diff --git a/tests/run/typeclass-derivation2c.check b/tests/run/typeclass-derivation2c.check new file mode 100644 index 000000000000..eee9a3d088a3 --- /dev/null +++ b/tests/run/typeclass-derivation2c.check @@ -0,0 +1,10 @@ +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) +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 new file mode 100644 index 000000000000..eb4db03901df --- /dev/null +++ b/tests/run/typeclass-derivation2c.scala @@ -0,0 +1,485 @@ +import scala.collection.mutable +import scala.annotation.tailrec + +// Simulation of an alternative typeclass derivation scheme proposed in #6153 + +// -- Classes and Objects of the Derivation Framework ---------------------------------- + +/** Core classes. In the current implementation these are in the scala.reflect package */ +object Deriving { + + /** The Generic class hierarchy allows typelevel access to + * enums, case classes and objects, and their sealed parents. + */ + sealed abstract class Generic[T] + object Generic { + + /** 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 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): Generic[_ <: T] = ??? + } + + /** 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 + } + } + } + + /** 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._ + +// -- 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 { + + 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) <: Generic[_ <: 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 extends Generic.Product[Cons[_]] { + def apply[T](x: T, xs: Lst[T]): Lst[T] = new Cons(x, xs) + + 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") + } = this.asInstanceOf + } + + 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 + 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 simple product type +case class Pair[T](x: T, y: T) // derives Eq, Pickler, Show + +object Pair extends Generic.Product[Pair[_]] { + + 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") + } = this.asInstanceOf + + // 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 +} + +// Another sum type +sealed trait Either[+L, +R] extends Product with Serializable // derives Eq, Pickler, Show + +object Either { + class GenericEither[L, R] extends Generic.Sum[Either[L, R]] { + def ordinal(x: Either[L, R]) = x match { + case x: Left[_] => 0 + case x: Right[_] => 1 + } + inline override def numberOfCases = 2 + inline override def alternative(n: Int) <: Generic[_ <: 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 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 + } = this.asInstanceOf +} + +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 + } = this.asInstanceOf +} + +// -- 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] { + def eql(x: T, y: T): Boolean +} + +object Eq { + import scala.compiletime.erasedValue + + 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: Any, y: Any): Boolean = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + 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: 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, y) + case g: Generic.Singleton[_] => true + } + 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 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 + } + } + + 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 +} + +object Pickler { + import scala.compiletime.{erasedValue, constValue} + + 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](n: Int)(buf: mutable.ListBuffer[Int], x: Any): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + 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: 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) + case g: Generic.Singleton[s] => + } + 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) + } + + 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](n + 1)(buf, elems) + case _: Unit => + } + + inline def unpickleProduct[T](g: Generic.Product[T])(buf: mutable.ListBuffer[Int]): T = { + inline val size = constValue[Tuple.Size[g.ElemTypes]] + 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 = + inline if (n == g.numberOfCases) + throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ord") + else if (ord == n) + inline g.alternative(n) match { + case g: Generic.Product[p] => unpickleProduct(g)(buf) + case g: Generic.Singleton[s] => g.singletonValue + } + 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 g: Generic.Sum[T] => + val ord = g.ordinal(x) + buf += ord + 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 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] + } + } + + 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) + } +} + +// Display type class, making use of label info. +trait Show[T] { + def show(x: T): String +} +object Show { + import scala.compiletime.{erasedValue, constValue} + + inline def tryShow[T](x: T): String = implicit match { + case s: Show[T] => s.show(x) + } + + 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(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: Any): String = { + val labl = constValue[g.CaseLabel] + 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 = + inline if (n == g.numberOfCases) + "" + else if (ord == n) + inline g.alternative(n) match { + case g: Generic.Product[p] => showProduct(g)(x) + case g: Generic.Singleton[s] => constValue[g.CaseLabel] + } + 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 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] + } + } + + implicit object IntShow extends Show[Int] { + def show(x: Int): String = x.toString + } +} + +// -- Tests ---------------------------------------------------------------------- + +object Test extends App { + 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)) +}