diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e3bcd1e1fda0..89e660d67e3b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -597,6 +597,29 @@ class Definitions { def Not_value(implicit ctx: Context) = NotModule.requiredMethod(nme.value) + // Generic classes + lazy val RepresentableType = ctx.requiredClassRef("dotty.generic.Representable") + def RepresentableClass(implicit ctx: Context) = RepresentableType.symbol.asClass + def RepresentableModule(implicit ctx: Context) = RepresentableClass.companionModule + lazy val PNilType = ctx.requiredClassRef("dotty.generic.PNil") + def PNilClass(implicit ctx: Context) = PNilType.symbol.asClass + def PNilModule(implicit ctx: Context) = PNilClass.companionModule + lazy val PConsType = ctx.requiredClassRef("dotty.generic.PCons") + def PConsClass(implicit ctx: Context) = PConsType.symbol.asClass + def PConsModule(implicit ctx: Context) = PConsClass.companionModule + lazy val SNilType = ctx.requiredClassRef("dotty.generic.SNil") + def SNilClass(implicit ctx: Context) = SNilType.symbol.asClass + def SNilModule(implicit ctx: Context) = SNilClass.companionModule + lazy val SConsType = ctx.requiredClassRef("dotty.generic.SCons") + def SConsClass(implicit ctx: Context) = SConsType.symbol.asClass + def SConsModule(implicit ctx: Context) = SConsClass.companionModule + lazy val SLeftType = ctx.requiredClassRef("dotty.generic.SLeft") + def SLeftClass(implicit ctx: Context) = SLeftType.symbol.asClass + def SLeftModule(implicit ctx: Context) = SLeftClass.companionModule + lazy val SRightType = ctx.requiredClassRef("dotty.generic.SRight") + def SRightClass(implicit ctx: Context) = SRightType.symbol.asClass + def SRightModule(implicit ctx: Context) = SRightClass.companionModule + lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope") // Annotation base classes diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index be886b2b3bd7..19763e2df3f1 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -723,7 +723,7 @@ object StdNames { def newBitmapName(bitmapPrefix: TermName, n: Int): TermName = bitmapPrefix ++ n.toString - def selectorName(n: Int): TermName = "_" + (n + 1) + def selectorName(n: Int): TermName = productAccessorName(n + 1) object primitive { val arrayApply: TermName = "[]apply" diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 0bec80c49cb9..51f1ef006dee 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -39,12 +39,10 @@ import reporting.diagnostic.messages.{NotAMember, SuperCallsNotAllowedInline} * * (9) Adds SourceFile annotations to all top-level classes and objects * - * (10) Adds Child annotations to all sealed classes - * - * (11) Minimizes `call` fields of `Inline` nodes to just point to the toplevel + * (10) Minimizes `call` fields of `Inline` nodes to just point to the toplevel * class from which code was inlined. * - * (12) Converts GADT bounds into normal type bounds + * (11) Converts GADT bounds into normal type bounds * * The reason for making this a macro transform is that some functions (in particular * super and protected accessors and instantiation checks) are naturally top-down and @@ -104,11 +102,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase private def transformAnnot(annot: Annotation)(implicit ctx: Context): Annotation = annot.derivedAnnotation(transformAnnot(annot.tree)) - private def transformMemberDef(tree: MemberDef)(implicit ctx: Context): Unit = { - val sym = tree.symbol - sym.registerIfChild() - sym.transformAnnotations(transformAnnot) - } + private def transformMemberDef(tree: MemberDef)(implicit ctx: Context): Unit = + tree.symbol.transformAnnotations(transformAnnot) private def transformSelect(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = { val qual = tree.qualifier diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 302d19924d13..3e3a1f6e190e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -36,6 +36,8 @@ import config.Config import config.Printers.{implicits, implicitsDetailed, typr} import collection.mutable import reporting.trace +import annotation.tailrec +import transform.SymUtils._ /** Implicit resolution */ object Implicits { @@ -562,7 +564,7 @@ trait Implicits { self: Typer => /** If `formal` is of the form ClassTag[T], where `T` is a class type, * synthesize a class tag for `T`. - */ + */ def synthesizedClassTag(formal: Type)(implicit ctx: Context): Tree = formal.argTypes match { case arg :: Nil => @@ -585,7 +587,7 @@ trait Implicits { self: Typer => /** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for * either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution. - */ + */ def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = { //println(i"synth eq $formal / ${formal.argTypes}%, %") formal.argTypes match { @@ -598,6 +600,164 @@ trait Implicits { self: Typer => } } + /** Synthesized dotty.generic.Representable instance when `formal` is + * a case class or of sealed class. + */ + def synthesizedRepresentable(formal: Type)(implicit ctx: Context): Tree = { + val generated = formal.baseType(defn.RepresentableClass).argTypes match { + case (arg @ _) :: Nil => + val A = fullyDefinedType(arg, "Representable argument", pos).stripTypeVar + if (A.typeSymbol.is(Sealed)) + synthesizedRepresentableSum(A) + else if (defn.isProductSubType(A)) + synthesizedRepresentableProduct(A) + else + EmptyTree + case _ => EmptyTree + } + + val typed = typedUnadapted(generated) + adapt(typed, formal) + } + + /** Assuming A is a sum, synthesize a Representable instance of the following shape: + * + * ``` + * new Representable[A] { + * type Repr = SCons[T1, SCons[T2, ..., SCons[Tn, SNil]]] + * + * def to(a: A): Repr = (a: @unchecked) match { + * case x: T1 => SLeft(x) + * case x: T2 => SRight(SLeft(x)) + * ... + * case x: Tn => SRight(SRight(...SRight(SLeft(x)))) + * } + * + * def from(r: Repr): A = (r: @unchecked) match { + * case SLeft(x) => x + * case SRight(SLeft(x)) => x + * ... + * case SRight(SRight(...SRight(SLeft(x)))) => x + * } + * ``` + */ + def synthesizedRepresentableSum(A: Type): untpd.Tree = { + import untpd._ + // TODO: Factor SpaceEngine.instantiate out of pattern matching logic. + // I'm not sure where would be the best place to put it... + import dotty.tools.dotc.transform.patmat.SpaceEngine + + val sumTypes = + A .classSymbol.children.map(_.namedType) + .map(t => new SpaceEngine().instantiate(t, A)) // Instantiate type parameters + .reverse // Reveresed to match source order + + val sumTypesSize = sumTypes.size + val noBounds = TypeBoundsTree(EmptyTree, EmptyTree) + + val repr = TypeDef("Repr".toTypeName, + sumTypes.zipWithIndex.foldRight[Tree](TypeTree(defn.SNilType)) { case ((cur, i), acc) => + AppliedTypeTree(TypeTree(defn.SConsType), TypeTree(cur) :: acc :: Nil) + } + ) + + def unchecked(t: Tree): Tree = Annotated(t, untpd.New(TypeTree(defn.UncheckedAnnotType), Nil)) + + val to: Tree = { + val arg = makeSyntheticParameter(tpt = TypeTree(A)) + val cases = { + val x = nme.syntheticParamName(0) + sumTypes.zipWithIndex.map { case (tpe, depth) => + val rhs = (1 to depth).foldLeft[Tree](Apply(tpd.ref(defn.SLeftModule), Ident(x))) { + case (acc, _) => Apply(tpd.ref(defn.SRightModule), acc) + } + CaseDef(Typed(Ident(x), TypeTree(tpe.widen)), EmptyTree, rhs) + } + } + val body = Match(unchecked(Ident(arg.name)), cases) + DefDef("to".toTermName, Nil, (arg :: Nil) :: Nil, Ident(repr.name), body).withFlags(Synthetic) + } + + val from: Tree = { + val arg = makeSyntheticParameter(tpt = Ident(repr.name)) + val cases = { + val x = nme.syntheticParamName(0) + (1 to sumTypesSize).map { depth => + val pat = (1 until depth).foldLeft(Apply(tpd.ref(defn.SLeftModule), Ident(x))) { + case (acc, _) => Apply(tpd.ref(defn.SRightModule), acc) + } + CaseDef(pat, EmptyTree, Ident(x)) + }.toList + } + val matsh = Match(unchecked(Ident(arg.name)), cases) + val body = TypeApply(Select(matsh, nme.asInstanceOf_), TypeTree(A) :: Nil) + DefDef("from".toTermName, Nil, (arg :: Nil) :: Nil, TypeTree(A), body).withFlags(Synthetic) + } + + val parents = TypeTree(defn.RepresentableType.appliedTo(A)) :: Nil + New(Template(emptyConstructor, parents, EmptyValDef, repr :: to :: from :: Nil)).withPos(pos) + } + + /** Assuming A is a product, synthesize a Representable instance of the following shape: + * + * ``` + * new Representable[A] { + * type Repr = PCons[T1, PCons[T2, ..., PCons[Tn, PNil]]] + * + * def to(a: A): Repr = + * PCons(a._1, (PCons(a._2, ... PCons(a._n, PNil())))) + * + * def from(r: Repr): A = r match { + * case PCons(_1, Pcons(_2, ..., Pcons(_n, PNil()))) => new A(_1, _2, ..., _n) + * } + * ``` + */ + def synthesizedRepresentableProduct(A: Type): untpd.Tree = { + import untpd._ + + val productTypes = productSelectorTypes(A) + val productTypesSize = productTypes.size + val noBounds = TypeBoundsTree(EmptyTree, EmptyTree) + + val repr = TypeDef("Repr".toTypeName, + productTypes.foldRight[Tree](TypeTree(defn.PNilType)) { case (cur, acc) => + AppliedTypeTree(TypeTree(defn.PConsType), TypeTree(cur) :: acc :: Nil) + } + ) + val pNil = Apply(tpd.ref(defn.PNilModule), Nil) + + val to = { + val arg = makeSyntheticParameter(tpt = TypeTree(A)) + val body = (1 to productTypesSize).foldRight(pNil) { case (i, acc) => + Apply(tpd.ref(defn.PConsModule), Select(Ident(arg.name), nme.productAccessorName(i)) :: acc :: Nil) + } + DefDef("to".toTermName, Nil, (arg :: Nil) :: Nil, Ident(repr.name), body).withFlags(Synthetic) + } + + val from = { + val arg = makeSyntheticParameter(tpt = Ident(repr.name)) + val pat = (1 to productTypesSize).reverse.foldLeft(pNil) { case (acc, i) => + Apply(tpd.ref(defn.PConsModule), Ident(nme.productAccessorName(i)) :: acc :: Nil) + } + val neuu = { + A match { + case tpe: NamedType if tpe.termSymbol.exists => // case object + ref(tpe) + case _ if A.classSymbol.exists => // case class + val newArgs = (1 to productTypesSize).map(i => Ident(nme.productAccessorName(i))).toList + val hasVarargs = A.classSymbol.primaryConstructor.info.isVarArgsMethod + val newArgs0 = if (hasVarargs) newArgs.init :+ repeated(newArgs.last) else newArgs + New(TypeTree(A), newArgs0 :: Nil) + } + } + val body = Match(Ident(arg.name), CaseDef(pat, EmptyTree, neuu) :: Nil) + DefDef("from".toTermName, Nil, (arg :: Nil) :: Nil, TypeTree(A), body).withFlags(Synthetic) + } + + val parents = TypeTree(defn.RepresentableType.appliedTo(A)) :: Nil + New(Template(emptyConstructor, parents, EmptyValDef, repr :: to :: from :: Nil)).withPos(pos) + } + def hasEq(tp: Type): Boolean = inferImplicit(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).isSuccess @@ -645,6 +805,8 @@ trait Implicits { self: Typer => synthesizedClassTag(formalValue).orElse(tree) else if (formalValue.isRef(defn.EqClass)) synthesizedEq(formalValue).orElse(tree) + else if (formalValue.isRef(defn.RepresentableClass)) + synthesizedRepresentable(formalValue).orElse(tree) else tree } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1519780ea8be..1b6a61134af9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1476,6 +1476,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit cls.registerTree(cdef1) + // Adds Child annotations to sealed classes + if (!cdef1.symbol.denot.isRefinementClass) + cdef1.symbol.registerIfChild() + cdef1 // todo later: check that diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 8b2a76164a81..8f63720b378e 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -54,14 +54,14 @@ trait ParallelTesting extends RunnerOrchestration { self => def runClassPath: String = outDir.getAbsolutePath + ":" + flags.runClassPath - def title: String = self match { + def title: String = (self match { case self: JointCompilationSource => if (self.files.length > 1) name else self.files.head.getPath case self: SeparateCompilationSource => self.dir.getPath - } + }).stripPrefix("../") /** Adds the flags specified in `newFlags0` if they do not already exist */ def withFlags(newFlags0: String*) = { diff --git a/library/src/dotty/generic/Prod.scala b/library/src/dotty/generic/Prod.scala new file mode 100644 index 000000000000..24c43c3a9077 --- /dev/null +++ b/library/src/dotty/generic/Prod.scala @@ -0,0 +1,12 @@ +package dotty.generic + +/** Represents a heterogeneous collection: a datatype capable of storing data + * of different types. Generalises Tuples to arbitrary arity. + */ +sealed trait Prod + +/** Non-empty product */ +final case class PCons[H, T <: Prod](head: H, tail: T) extends Prod + +/** Empty product */ +final case class PNil() extends Prod diff --git a/library/src/dotty/generic/Representable.scala b/library/src/dotty/generic/Representable.scala new file mode 100644 index 000000000000..2814f2ed3c82 --- /dev/null +++ b/library/src/dotty/generic/Representable.scala @@ -0,0 +1,21 @@ +package dotty.generic + +/** A generic representation of `A` as a sum of products. Provides back and + * forth conversions between values of type `A` and values of type `Repr`. + * Instances of `Representable` for sealed traits and case classes are + * automatically synthesized by the compiler when requested implicitly. + */ +trait Representable[A] { + /** Type of the generic representation of `A`, composed of `Sum` and `Prod` types. */ + type Repr // <: Sum | Prod ← when we stop cross compiling + + /** Converts a value of type `A` to it's generic representation */ + def to(a: A): Repr + + /** Converts a value in the generic representation of `A` to its concrete representation. */ + def from(r: Repr): A +} + +object Representable { + def apply[A](implicit r: Representable[A]): r.type = r +} diff --git a/library/src/dotty/generic/Sum.scala b/library/src/dotty/generic/Sum.scala new file mode 100644 index 000000000000..3f4564a24eb9 --- /dev/null +++ b/library/src/dotty/generic/Sum.scala @@ -0,0 +1,18 @@ +package dotty.generic + +/** Represents a value of several possible types. An instance of Sum is either + * an instance of SLeft or of SRight. Generalises Either to arbitrary arity. + */ +sealed trait Sum + +/** Non-empty sum */ +sealed trait SCons[H, T <: Sum] extends Sum + +/** Left case of a sum */ +final case class SLeft[H, T <: Sum](head: H) extends SCons[H, T] + +/** Right case of a sum */ +final case class SRight[H, T <: Sum](tail: T) extends SCons[H, T] + +/** Empty sum */ +sealed trait SNil extends Sum diff --git a/tests/run/generic-product.scala b/tests/run/generic-product.scala new file mode 100644 index 000000000000..b49958720a73 --- /dev/null +++ b/tests/run/generic-product.scala @@ -0,0 +1,33 @@ +import dotty.generic._ + +case class Foo(i: Int, s: String) +case class Bar() + +object Test { + def main(args: Array[String]): Unit = { + testFoo() + testBar() + } + + def testFoo(): Unit = { + // Representable can be synthesised via implicit search + val g = Representable[Foo] + + // Type is inferred correctly + val a: Representable[Foo] { type Repr = PCons[Int, PCons[String, PNil]] } = g + + // Representable#to and Representable#from behave as expected: + assert(g.from(g.to(Foo(1, "s"))) == Foo(1, "s")) + } + + def testBar(): Unit = { + // Representable can be synthesised via implicit search + val g = Representable[Bar] + + // Type is inferred correctly + val a: Representable[Bar] { type Repr = PNil } = g + + // Representable#to and Representable#from behave as expected: + assert(g.from(g.to(Bar())) == Bar()) + } +} diff --git a/tests/run/generic-sum.scala b/tests/run/generic-sum.scala new file mode 100644 index 000000000000..b1c85fdcdbea --- /dev/null +++ b/tests/run/generic-sum.scala @@ -0,0 +1,19 @@ +import dotty.generic._ + +sealed trait Foo +case class Bar(i: Int) extends Foo +case class Bus(s: String) extends Foo + +object Test { + def main(args: Array[String]): Unit = { + // Representable can be synthesised via implicit search + val g = Representable[Foo] + + // Type is inferred correctly + val a: Representable[Foo] { type Repr = SCons[Bar, SCons[Bus, SNil]] } = g + + // Representable#to and Representable#from behave as expected: + assert(g.from(g.to(Bar(1))) == Bar(1)) + assert(g.from(g.to(Bus("s"))) == Bus("s")) + } +} diff --git a/tests/run/representable.scala b/tests/run/representable.scala new file mode 100644 index 000000000000..f1e903ad37b1 --- /dev/null +++ b/tests/run/representable.scala @@ -0,0 +1,811 @@ +import dotty.generic._ + +// Adapted from shapeless' Generic test suite available at +// https://github.com/milessabin/shapeless/blob/shapeless-2.3.2/core/src/test/scala/shapeless/generic.scala + +object Syntax { + type &:[H, T <: Prod] = PCons[H, T] + type |:[H, T <: Sum] = SCons[H, T] + + implicit class ProdSyntax1[T <: Prod](t: T) extends AnyVal { + def &:[H](h: H): PCons[H, T] = PCons(h, t) + } +} + +import Syntax._ + +object RepresentableTestsAux { + sealed trait Fruit + case class Apple() extends Fruit + case class Banana() extends Fruit + case class Orange() extends Fruit + case class Pear() extends Fruit + + sealed trait AbstractSingle + case class Single() extends AbstractSingle + + sealed trait Tree[T] + case class Node[T](left: Tree[T], right: Tree[T]) extends Tree[T] + case class Leaf[T](t: T) extends Tree[T] + + sealed trait Enum + case object A extends Enum + case object B extends Enum + case object C extends Enum + + sealed trait L + case object N extends L + case class C(hd: Int, tl: L) extends L + + case class Company(depts : List[Dept]) + sealed trait Subunit + case class Dept(name : String, manager : Employee, subunits : List[Subunit]) extends Subunit + case class Employee(person : Person, salary : Salary) extends Subunit + case class Person(name : String, address : String, age: Int) + + case class Salary(salary : Double) + + case class PersonWithPseudonims(name: String, nicks: String*) + + case class PersonWithPseudonimsT[T](name: T, nicks: T*) + + class NonCCLazy(prev0: => NonCCLazy, next0: => NonCCLazy) { + lazy val prev = prev0 + lazy val next = next0 + } + + sealed trait Xor[+A, +B] + case class Left[+LA](a: LA) extends Xor[LA, Nothing] + case class Right[+RB](b: RB) extends Xor[Nothing, RB] + + sealed trait Base[BA, BB] + case class Swap[SA, SB](a: SA, b: SB) extends Base[SB, SA] + + sealed trait Overlapping + sealed trait OA extends Overlapping + case class OAC(s: String) extends OA + sealed trait OB extends Overlapping + case class OBC(s: String) extends OB + case class OAB(i: Int) extends OA with OB +} + +object RepresentableTests { + import RepresentableTestsAux._ + + type APBO = Apple |: Banana |: Orange |: Pear |: SNil + type ABC = A.type |: B.type |: C.type |: SNil + + def testProductBasics(): Unit = { + val p = Person("Joe Soap", "Brighton", 23) + type SSI = String &: String &: Int &: PNil + val gen = Representable[Person] + + val p0 = gen.to(p) + identity[SSI](p0) + assert(("Joe Soap" &: "Brighton" &: 23 &: PNil()) == p0) + + val p1 = gen.from(p0) + identity[Person](p1) + assert(p == p1) + } + + def testProductVarargs(): Unit = { + val p = PersonWithPseudonims("Joe Soap", "X", "M", "Z") + val gen = Representable[PersonWithPseudonims] + + val p0 = gen.to(p) + // identity[String &: Seq[String] &: PNil](p0) + assert(("Joe Soap" &: Seq("X", "M", "Z") &: PNil()) == p0) + + val p1 = gen.from(p0) + identity[PersonWithPseudonims](p1) + assert(p == p1) + } + + def testTuples(): Unit = { + val gen1 = Representable[Tuple1[Int]] + identity[Representable[Tuple1[Int]] { type Repr = Int &: PNil }](gen1) + + val gen2 = Representable[(Int, String)] + identity[Representable[(Int, String)] { type Repr = Int &: String &: PNil }](gen2) + + val gen3 = Representable[(Int, String, Boolean)] + identity[Representable[(Int, String, Boolean)] { type Repr = Int &: String &: Boolean &: PNil }](gen3) + } + + def testCoproductBasics(): Unit = { + val a: Fruit = Apple() + val p: Fruit = Pear() + val b: Fruit = Banana() + val o: Fruit = Orange() + + val gen = Representable[Fruit] + + val a0 = gen.to(a) + identity[APBO](a0) + + val p0 = gen.to(p) + + identity[APBO](p0) + + val b0 = gen.to(b) + + identity[APBO](b0) + + val o0 = gen.to(o) + + identity[APBO](o0) + + val a1 = gen.from(a0) + identity[Fruit](a1) + + val p1 = gen.from(p0) + identity[Fruit](p1) + + val b1 = gen.from(b0) + identity[Fruit](b1) + + val o1 = gen.from(o0) + identity[Fruit](o1) + } + + def testSingletonCoproducts(): Unit = { + type S = Single + + val gen = Representable[AbstractSingle] + + val s: AbstractSingle = Single() + + val s0 = gen.to(s) + identity[(Single |: SNil)](s0) + + val s1 = gen.from(s0) + identity[AbstractSingle](s1) + } + + // Dotty deviation: we infer `type Repr = OA |: OB |: SNil` + // instead of `type Repr = OAB |: OAC |: OBC |: SNil` in this case. + def testOverlappingCoproducts(): Unit = { + val gen = Representable[Overlapping] + + val o: Overlapping = OAB(1) + val o0 = gen.to(o) + identity[(OA |: OB |: SNil)](o0) + + val o1 = gen.from(o0) + identity[Overlapping](o1) + } + + def testCaseObjects(): Unit = { + val a: Enum = A + val b: Enum = B + val c: Enum = C + + val gen = Representable[Enum] + + val a0 = gen.to(a) + identity[ABC](a0) + + val b0 = gen.to(b) + identity[ABC](b0) + + val c0 = gen.to(c) + identity[ABC](c0) + + val a1 = gen.from(a0) + identity[Enum](a1) + + val b1 = gen.from(b0) + identity[Enum](b1) + + val c1 = gen.from(c0) + identity[Enum](c1) + } + + def testParametrized(): Unit = { + val t: Tree[Int] = Node(Node(Leaf(23), Leaf(13)), Leaf(11)) + type NI = Node[Int] |: Leaf[Int] |: SNil + + val gen = Representable[Tree[Int]] + + val t0 = gen.to(t) + identity[NI](t0) + + val t1 = gen.from(t0) + identity[Tree[Int]](t1) + } + + def testParametrizedWithVarianceOption(): Unit = { + val o: Option[Int] = Option(23) + type SN = None.type |: Some[Int] |: SNil + + val gen = Representable[Option[Int]] + + val o0 = gen.to(o) + identity[SN](o0) + + val o1 = gen.from(o0) + identity[Option[Int]](o1) + } + + def testParametrizedWithVarianceList(): Unit = { + val l: List[Int] = List(1, 2, 3) + type CN = ::[Int] |: scala.collection.immutable.Nil.type |: SNil + + val gen = Representable[List[Int]] + + val l0 = gen.to(l) + identity[CN](l0) + + val l1 = gen.from(l0) + identity[List[Int]](l1) + } + + def testParametrzedSubset(): Unit = { + val l = Left(23) + val r = Right(true) + type IB = Left[Int] |: Right[Boolean] |: SNil + + val gen = Representable[Xor[Int, Boolean]] + + val c0 = gen.to(l) + val d0 = SLeft(l) + identity[IB](c0) + assert(d0 == c0) + + val c1 = gen.to(r) + val d1 = SRight(SLeft(r)) + identity[IB](c1) + assert(d1 == c1) + } + + def testParametrizedPermute(): Unit = { + val s = Swap(23, true) + type IB = Swap[Int, Boolean] |: SNil + + val gen = Representable[Base[Boolean, Int]] + + val s0 = gen.to(s) + val s1 = SLeft(s) + identity[IB](s0) + assert(s1 == s0) + } + + trait Parent { + case class Nested(i: Int, s: String) + + sealed abstract class Foo extends Product with Serializable + + case object A extends Foo + case object B extends Foo + case class C() extends Foo + } + + trait Child extends Parent { self => + val gen = Representable[Nested] + val adtGen = Representable[Foo] + } + + object O extends Child + + def testNestedInherited(): Unit = { + val n0 = O.Nested(23, "foo") + val repr = O.gen.to(n0) + identity[(Int &: String &: PNil)](repr) + val n1 = O.gen.from(repr) + identity[O.Nested](n1) + assert(n0 == n1) + + { + val foo0 = O.B + val repr = O.adtGen.to(foo0) + identity[(O.A.type |: O.B.type |: O.C |: SNil)](repr) + } + + { + val foo0 = O.C() + val repr = O.adtGen.to(foo0) + identity[(O.A.type |: O.B.type |: O.C |: SNil)](repr) + } + } + + def testNonRepresentable(): Unit = { + import scala.implicits.Not + + implicitly[Not[Representable[Int]]] + implicitly[Not[Representable[Array[Int]]]] + implicitly[Not[Representable[String]]] + implicitly[Not[Representable[PNil]]] + implicitly[Not[Representable[(Int &: String &: PNil)]]] + implicitly[Not[Representable[SNil]]] + implicitly[Not[Representable[(Int |: String |: SNil)]]] + } + + sealed trait Color + case object Green extends Color + object Color { + case object Red extends Color + } + + def testNestedCaseObjects(): Unit = { + val a: Option[Green.type] = None + Representable[Green.type] + Representable[Color.Red.type] + } + + sealed trait Base1 + case object Foo1 extends Base1 + case object Bar1 extends Base1 + + trait TC[T] + + object TC { + def apply[T](implicit tc: TC[T]): TC[T] = tc + + implicit def hnilTC: TC[PNil] = new TC[PNil] {} + implicit def hconsTC[H, T <: Prod](implicit hd: => TC[H], tl: => TC[T]): TC[H &: T] = new TC[H &: T] {} + + implicit def cnilTC: TC[SNil] = new TC[SNil] {} + implicit def cconsTC[H, T <: Sum](implicit hd: => TC[H], tl: => TC[T]): TC[H |: T] = new TC[H |: T] {} + + implicit def projectTC[F, G](implicit gen: Representable[F] { type Repr = G }, tc: => TC[G]): TC[F] = new TC[F] {} + } + + def testCaseObjectsAndLazy(): Unit = { + TC[Base1] + } +} + +object RepresentableTestsAux2 { + sealed trait Foo[T] + + object Foo { + implicit def derivePNil: Foo[PNil] = ??? + + implicit def deriveRepresentable[A, Rec <: Prod] + (implicit gen: Representable[A] { type Repr = Rec }, auto: Foo[Rec]): Foo[A] = ??? + } + + sealed class Bar[A] + + object Bar extends Bar0 { + implicit def cnil: Bar[SNil] = ??? + } + + trait Bar0 { + implicit def deriveCoproduct[H, T <: Sum] + (implicit headFoo: Foo[H], tailAux: Bar[T]): Bar[(H |: T)] = ??? + + implicit def representable[A, U <: Sum] + (implicit gen: Representable[A] { type Repr = U }, auto: Bar[U]): Bar[A] = ??? + } + + // TODO "Unexpected tree in genLoad", somehow `untpd.ref` generates something + // that survives until backend but shouldn't. To be investigated... + // Unexpected tree in genLoad: TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class generic)),module class RepresentableTestsAux2$)),class Outer1)),module class RepresentableTestsAux2$Outer1$Inner$)] + // class Outer1 { + // sealed trait Color + // object Inner { + // case object Red extends Color + // } + // implicit val r: Representable[Bar[Color]] { type Repr = SNil } = null + // implicitly[Bar[Color]] + // } + + object Outer2 { + class Wrapper { + sealed trait Color + } + val wrapper = new Wrapper + import wrapper.Color + case object Red extends Color + case object Green extends Color + case object Blue extends Color + + implicitly[Bar[Color]] + } + + object Outer3 { + class Wrapper { + sealed trait Color + } + val wrapper = new Wrapper + case object Red extends wrapper.Color + case object Green extends wrapper.Color + case object Blue extends wrapper.Color + + implicitly[Bar[wrapper.Color]] + } + + object Outer4 { + val wrapper = new Wrapper + case object Red extends wrapper.Color + case object Green extends wrapper.Color + case object Blue extends wrapper.Color + + class Wrapper { + sealed trait Color + implicitly[Bar[wrapper.Color]] + } + } + + object Outer5 { + trait Command + object Command { + sealed trait Execution extends Command + } + + case class Buzz() extends Command.Execution + case class Door() extends Command.Execution + + Representable[Command.Execution] + } +} + +object MixedCCNonCCNested { + // #3637 + // { + // object T1 { + // sealed abstract class Tree + // final case class Node(left: Tree, right: Tree, v: Int) extends Tree + // case object Leaf extends Tree + // } + + // // Representable[T1.Tree] + // import T1._ + // // Representable[Tree] + + // sealed trait A + // sealed case class B(i: Int, s: String) extends A + // case object C extends A + // sealed trait D extends A + // final case class E(a: Double, b: Option[Float]) extends D + // case object F extends D + // sealed abstract class Foo extends D + // case object Baz extends Foo + // final case class Bar() extends Foo + // final case class Baz(i1: Int, s1: String) extends Foo + + // // Representable[A] + // // Representable[B] + // // Representable[C.type] + // // Representable[D] + // // Representable[E] + // // Representable[F.type] + // // Representable[Foo] + // // Representable[Baz.type] + // // Representable[Bar] + // // Representable[Baz] + // } + + def methodLocal: Unit = { + object T1 { + sealed abstract class Tree + final case class Node(left: Tree, right: Tree, v: Int) extends Tree + case object Leaf extends Tree + } + + Representable[T1.Tree] + import T1._ + Representable[Tree] + + sealed trait A + sealed case class B(i: Int, s: String) extends A + case object C extends A + sealed trait D extends A + final case class E(a: Double, b: Option[Float]) extends D + case object F extends D + sealed abstract class Foo extends D + case object Baz extends Foo + final case class Bar() extends Foo + final case class Baz(i1: Int, s1: String) extends Foo + + Representable[A] + Representable[B] + Representable[C.type] + Representable[D] + Representable[E] + Representable[F.type] + Representable[Foo] + Representable[Baz.type] + Representable[Bar] + Representable[Baz] + } + + // Top level + object T1 { + sealed abstract class Tree + final case class Node(left: Tree, right: Tree, v: Int) extends Tree + case object Leaf extends Tree + } + + Representable[T1.Tree] + import T1._ + Representable[Tree] + + sealed trait A + sealed case class B(i: Int, s: String) extends A + case object C extends A + sealed trait D extends A + final case class E(a: Double, b: Option[Float]) extends D + case object F extends D + sealed abstract class Foo extends D + case object Baz extends Foo + final case class Bar() extends Foo + final case class Baz(i1: Int, s1: String) extends Foo + + Representable[A] + Representable[B] + Representable[C.type] + Representable[D] + Representable[E] + Representable[F.type] + Representable[Foo] + Representable[Baz.type] + Representable[Bar] + Representable[Baz] +} + +object EnumDefns1 { + sealed trait EnumVal + object BarA extends EnumVal { val name = "A" } + object BarB extends EnumVal { val name = "B" } + object BarC extends EnumVal { val name = "C" } +} + +object EnumDefns2 { + sealed trait EnumVal + case object BarA extends EnumVal { val name = "A" } + case object BarB extends EnumVal { val name = "B" } + case object BarC extends EnumVal { val name = "C" } +} + +object EnumDefns5 { + sealed trait EnumVal + object EnumVal { + object BarA extends EnumVal { val name = "A" } + object BarB extends EnumVal { val name = "B" } + object BarC extends EnumVal { val name = "C" } + } +} + +object EnumDefns6 { + sealed trait EnumVal + object EnumVal { + case object BarA extends EnumVal { val name = "A" } + case object BarB extends EnumVal { val name = "B" } + case object BarC extends EnumVal { val name = "C" } + } +} + +object TestEnum { + def testEnum1(): Unit = { + import EnumDefns1._ + + val gen = Representable[EnumVal] + val a0 = gen.to(BarA) + val a1 = SLeft[BarA.type, SNil](BarA) + assert(a0 == a1) + + val b0 = gen.to(BarB) + val b1 = SRight(SLeft[BarB.type, SNil](BarB)) + assert(b0 == b1) + + val c0 = gen.to(BarC) + val c1 = SRight(SRight(SLeft[BarC.type, SNil](BarC))) + assert(c0 == c1) + } + + def testEnum2(): Unit = { + import EnumDefns2._ + + val gen = Representable[EnumVal] + val a0 = gen.to(BarA) + val a1 = SLeft[BarA.type, SNil](BarA) + assert(a0 == a1) + + val b0 = gen.to(BarB) + val b1 = SRight(SLeft[BarB.type, SNil](BarB)) + assert(b0 == b1) + + val c0 = gen.to(BarC) + val c1 = SRight(SRight(SLeft[BarC.type, SNil](BarC))) + assert(c0 == c1) + } + + def testEnum5(): Unit = { + import EnumDefns5._ + import EnumVal._ + + val gen = Representable[EnumVal] + val a0 = gen.to(BarA) + val a1 = SLeft[BarA.type, SNil](BarA) + assert(a0 == a1) + + val b0 = gen.to(BarB) + val b1 = SRight(SLeft[BarB.type, SNil](BarB)) + assert(b0 == b1) + + val c0 = gen.to(BarC) + val c1 = SRight(SRight(SLeft[BarC.type, SNil](BarC))) + assert(c0 == c1) + } + + def testEnum6(): Unit = { + import EnumDefns6._ + import EnumVal._ + + val gen = Representable[EnumVal] + val a0 = gen.to(BarA) + val a1 = SLeft[BarA.type, SNil](BarA) + assert(a0 == a1) + + val b0 = gen.to(BarB) + val b1 = SRight(SLeft[BarB.type, SNil](BarB)) + assert(b0 == b1) + + val c0 = gen.to(BarC) + val c1 = SRight(SRight(SLeft[BarC.type, SNil](BarC))) + assert(c0 == c1) + } +} + +object TestPrefixes1 { + trait Defs { + case class CC(i: Int, s: String) + + sealed trait Sum + case class SumI(i: Int) extends Sum + case class SumS(s: String) extends Sum + } + + object Defs extends Defs + + object Derivations { + Representable[Defs.CC] + Representable[Defs.SumI] + Representable[Defs.SumS] + + Representable[Defs.Sum] + } +} + +// #3564, should work otherwise +// package TestSingletonMembers { +// case class CC(i: Int, s: "msg") + +// object Derivations2 { +// Representable[CC] +// } +// } + +// Rather tricky given than there is no infrastructure to get "reachable" +// children, see #3574. I would postpone these cases for later (this is also +// an issue in pattern matching exhaustivity) +// object PathVariantDefns { +// sealed trait AtomBase { +// sealed trait Atom +// case class Zero(value: String) extends Atom +// } + +// trait Atom1 extends AtomBase { +// case class One(value: String) extends Atom +// } + +// trait Atom2 extends AtomBase { +// case class Two(value: String) extends Atom +// } + +// object Atoms01 extends AtomBase with Atom1 +// object Atoms02 extends AtomBase with Atom2 +// } + +// object PathVariants { +// import PathVariantDefns._ + +// val gen1 = Representable[Atoms01.Atom] +// implicitly[gen1.Repr =:= (Atoms01.One |: Atoms01.Zero |: SNil)] + +// val gen2 = Representable[Atoms02.Atom] +// implicitly[gen2.Repr =:= (Atoms02.Two |: Atoms02.Zero |: SNil)] +// } + +object PrivateCtorDefns { + sealed trait PublicFamily + case class PublicChild() extends PublicFamily + private case class PrivateChild() extends PublicFamily +} + +object PrivateCtor { + import PrivateCtorDefns._ + + // illTyped Representable[PublicFamily] +} + +object Thrift { + object TProduct { + def apply(a: Double, b: String): TProduct = new Immutable(a, b) + + def unapply(tp: TProduct): Option[Product2[Double, String]] = Some(tp) + + class Immutable( + val a: Double, + val b: String, + val _passthroughFields: scala.collection.immutable.Map[Short, Byte] + ) extends TProduct { + def this( + a: Double, + b: String + ) = this( + a, + b, + Map.empty + ) + } + } + + trait TProduct extends Product2[Double, String] { + def a: Double + def b: String + + def _1 = a + def _2 = b + + override def productPrefix: String = "TProduct" + + def canEqual(t: Any): Boolean = true + } + + Representable[TProduct.Immutable] +} + +object HigherKinded { + type Id[A] = A + + sealed trait Foo[A[_]] + case class Bar[A[_]]() extends Foo[A] + + Representable[Bar[Id]] + Representable[Foo[Id]] + + sealed trait Pipo[A[_]] + case class Lino() extends Pipo[Id] + + Representable[Pipo[Id]] +} + +object Test { + def main(args: Array[String]): Unit = { + Syntax + RepresentableTestsAux + EnumDefns1 + EnumDefns2 + EnumDefns5 + EnumDefns6 + TestPrefixes1 + PrivateCtorDefns + PrivateCtor + Thrift + HigherKinded + RepresentableTests.testProductBasics() + RepresentableTests.testProductVarargs() + RepresentableTests.testTuples() + RepresentableTests.testCoproductBasics() + RepresentableTests.testSingletonCoproducts() + RepresentableTests.testOverlappingCoproducts() + RepresentableTests.testCaseObjects() + RepresentableTests.testParametrized() + RepresentableTests.testParametrizedWithVarianceOption() + RepresentableTests.testParametrizedWithVarianceList() + RepresentableTests.testParametrzedSubset() + RepresentableTests.testParametrizedPermute() + // RepresentableTests.testNestedInherited() // Fails at runtime because of #3624 + RepresentableTests.testNonRepresentable() + RepresentableTests.testNestedCaseObjects() + RepresentableTests.testCaseObjectsAndLazy() + TestEnum.testEnum1() + TestEnum.testEnum2() + TestEnum.testEnum5() + TestEnum.testEnum6() + } +}