From fe09e0d8fe68e0b48d5e864e1de12ae5ee86077d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jan 2017 14:46:24 +1100 Subject: ADT and Serialization test The test exercises all the improvements made in previous commits of this branch. --- tests/run/generic/Color.scala | 30 +++++++++ tests/run/generic/Enum.scala | 18 ++++++ tests/run/generic/List.scala | 89 ++++++++++++++++++++++++++ tests/run/generic/SearchResult.scala | 63 +++++++++++++++++++ tests/run/generic/Serialization.scala | 115 ++++++++++++++++++++++++++++++++++ tests/run/generic/Shapes.scala | 22 +++++++ tests/run/generic/Test.scala | 58 +++++++++++++++++ tests/run/generic/Tree.scala | 113 +++++++++++++++++++++++++++++++++ 8 files changed, 508 insertions(+) create mode 100644 tests/run/generic/Color.scala create mode 100644 tests/run/generic/Enum.scala create mode 100644 tests/run/generic/List.scala create mode 100644 tests/run/generic/SearchResult.scala create mode 100644 tests/run/generic/Serialization.scala create mode 100644 tests/run/generic/Shapes.scala create mode 100644 tests/run/generic/Test.scala create mode 100644 tests/run/generic/Tree.scala (limited to 'tests/run') diff --git a/tests/run/generic/Color.scala b/tests/run/generic/Color.scala new file mode 100644 index 000000000..ed248295d --- /dev/null +++ b/tests/run/generic/Color.scala @@ -0,0 +1,30 @@ +package generic + +import Shapes._ + +/** enum Color { + * case Red + * case Green + * case Blue + * } + */ +sealed trait Color extends Enum + +object Color extends EnumValues[Color](3) { + + private def $new(tag: Int, name: String) = new Color { + def enumTag = tag + override def toString = name + registerEnumValue(this) + } + + val Red: Color = $new(0, "Red") + val Green: Color = $new(1, "Green") + val Blue: Color = $new(2, "Blue") + + implicit val ColorShape: Color `shaped` EnumValue[Color] = + new (Color `shaped` EnumValue[Color]) { + def toShape(x: Color) = EnumValue(x.enumTag) + def fromShape(x: EnumValue[Color]) = Color.value(x.tag) + } +} \ No newline at end of file diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala new file mode 100644 index 000000000..dbdbfe8eb --- /dev/null +++ b/tests/run/generic/Enum.scala @@ -0,0 +1,18 @@ +package generic + +import Shapes.Singleton + +trait Enum { + def enumTag: Int +} + +trait FiniteEnum extends Enum + +abstract class EnumValues[E <: Enum](numVals: Int) { + private var myValues = new Array[AnyRef](numVals) + + def registerEnumValue(v: E) = + myValues(v.enumTag) = v + + def value: IndexedSeq[E] = (myValues: IndexedSeq[AnyRef]).asInstanceOf[IndexedSeq[E]] +} diff --git a/tests/run/generic/List.scala b/tests/run/generic/List.scala new file mode 100644 index 000000000..3f3657656 --- /dev/null +++ b/tests/run/generic/List.scala @@ -0,0 +1,89 @@ +package generic + +import Shapes._ + +/** enum List[T] { + * case Cons(x: T, xs: List[T]) + * case Nil() + * } + */ +sealed trait List0[T] extends Enum +object List0 { + abstract case class Cons[T](hd: T, tl: List0[T]) extends List0[T] { + def enumTag = 0 + } + object Cons { + def apply[T](x: T, xs: List0[T]): List0[T] = new Cons(x, xs) {} + implicit def ConsShape[T]: Cons[T] `shaped` Prod[T, List0[T]] = + new (Cons[T] `shaped` Prod[T, List0[T]]) { + def toShape(x: Cons[T]) = Prod(x.hd, x.tl) + def fromShape(p: Prod[T, List0[T]]) = new Cons(p.fst, p.snd) {} + } + } + + abstract case class Nil[T]() extends List0[T] { + def enumTag = 1 + } + object Nil { + def apply[T](): List0[T] = new Nil[T]() {} + implicit def NilShape[T]: Nil[T] `shaped` Unit = + new (Nil[T] `shaped` Unit) { + def toShape(x: Nil[T]) = () + def fromShape(x: Unit) = new Nil[T]() {} + } + } + + implicit def List0Shape[T]: List0[T] `shaped` Sum[Cons[T], Nil[T]] = + new (List0[T] `shaped` Sum[Cons[T], Nil[T]]) { + def toShape(x: List0[T]) = x match { + case x: Cons[T] => Fst(x) + case x: Nil[T] => Snd(x) + } + def fromShape(x: Sum[Cons[T], Nil[T]]) = x match { + case Fst(c) => c + case Snd(n) => n + } + } +} + +/** enum List[T] { + * case Cons(x: T, xs: List[T]) + * case Nil extends List[Nothing] + * } + */ +sealed trait List[+T] extends Enum +object List { + abstract case class Cons[T](hd: T, tl: List[T]) extends List[T] { + def enumTag = 0 + } + object Cons { + def apply[T](x: T, xs: List[T]): List[T] = new Cons(x, xs) {} + type Shape[T] = Prod[T, List[T]] + implicit def ConsShape[T]: Cons[T] `shaped` Shape[T] = + new (Cons[T] `shaped` Shape[T]) { + def toShape(x: Cons[T]) = Prod(x.hd, x.tl) + def fromShape(p: Shape[T]) = new Cons(p.fst, p.snd) {} + } + } + + val Nil = new List[Nothing] { + def enumTag = 1 + override def toString = "Nil" + } + + implicit def NilSingleton: Singleton[Nil.type] = new Singleton[Nil.type](Nil) + + type Shape[T] = Sum[Cons[T], Nil.type] + + implicit def ListShape[T]: List[T] `unfolds` Shape[T] = + new (List[T] `shaped` Shape[T]) { + def toShape(x: List[T]) = x match { + case x: Cons[T] => Fst(x) + case Nil => Snd(Nil) + } + def fromShape(x: Shape[T]): List[T] = x match { + case Fst(c) => c + case Snd(n) => n + } + } +} \ No newline at end of file diff --git a/tests/run/generic/SearchResult.scala b/tests/run/generic/SearchResult.scala new file mode 100644 index 000000000..be8ebd15d --- /dev/null +++ b/tests/run/generic/SearchResult.scala @@ -0,0 +1,63 @@ +package generic + +import Shapes._ + +/** enum SearchResult { + * case Success(result: Color) + * case Diverging + * case NoMatch + * case Ambiguous(alt1: SearchResult, alt2: SearchResult) + * } + */ +sealed trait SearchResult extends Enum + +object SearchResult extends EnumValues[SearchResult](2) { + + private def $new(tag: Int, name: String) = new SearchResult { + def enumTag = tag + override def toString = name + registerEnumValue(this) + } + + abstract case class Success(result: Color) extends SearchResult { + def enumTag = 0 + } + object Success { + def apply(result: Color): SearchResult = new Success(result) {} + implicit def SuccessShape: Success `shaped` Color = + new (Success `shaped` Color) { + def toShape(s: Success) = s.result + def fromShape(c: Color) = new Success(c) {} + } + } + + val Diverging = $new(1, "Diverging") + val NoMatch = $new(2, "NoMatch") + + abstract case class Ambiguous(alt1: SearchResult, alt2: SearchResult) extends SearchResult { + def enumTag = 3 + } + object Ambiguous { + def apply(alt1: SearchResult, alt2: SearchResult): SearchResult = new Ambiguous(alt1, alt2) {} + implicit def AmbiguousShape: Ambiguous `shaped` Prod[SearchResult, SearchResult] = + new (Ambiguous `shaped` Prod[SearchResult, SearchResult]) { + def toShape(a: Ambiguous) = Prod(a.alt1, a.alt2) + def fromShape(p: Prod[SearchResult, SearchResult]) = new Ambiguous(p.fst, p.snd) {} + } + } + + implicit def SearchResultShape: + SearchResult `shaped` Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]] = + new (SearchResult `shaped` Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]]) { + def toShape(x: SearchResult) = x match { + case x: Success => Fst(x) + case x: Ambiguous => Snd(Fst(x)) + case x => Snd(Snd(EnumValue(x.enumTag))) + } + def fromShape(x: Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]]): SearchResult = x match { + case Fst(s) => s + case Snd(Fst(a)) => a + case Snd(Snd(ev)) => value(ev.tag) + } + } +} \ No newline at end of file diff --git a/tests/run/generic/Serialization.scala b/tests/run/generic/Serialization.scala new file mode 100644 index 000000000..a82d6bc7a --- /dev/null +++ b/tests/run/generic/Serialization.scala @@ -0,0 +1,115 @@ +package generic + +import java.io.{DataInputStream,DataOutputStream} +import scala.collection.generic.GenericCompanion +import scala.collection.mutable.ArrayBuffer +import Shapes._ + +object Serialization { + + trait Serializable[T] { + def write(x: T, out: DataOutputStream): Unit + def read(in: DataInputStream): T + } + + implicit val UnitSerializable: Serializable[Unit] = + new Serializable[Unit] { + def write(x: Unit, out: DataOutputStream) = () + def read(in: DataInputStream) = () + } + + implicit def SingleSerializable[T](implicit + ev1: Singleton[T] + ): Serializable[T] = new Serializable[T] { + def write(x: T, out: DataOutputStream) = () + def read(in: DataInputStream) = ev1.value + } + + implicit def EnumValueSerializable[T]: Serializable[EnumValue[T]] = + new Serializable[EnumValue[T]] { + def write(x: EnumValue[T], out: DataOutputStream) = out.writeShort(x.tag) + def read(in: DataInputStream) = EnumValue(in.readShort()) + } + + implicit val BooleanSerializable: Serializable[Boolean] = + new Serializable[Boolean] { + def write(x: Boolean, out: DataOutputStream) = out.writeBoolean(x) + def read(in: DataInputStream) = in.readBoolean() + } + + implicit val IntSerializable: Serializable[Int] = + new Serializable[Int] { + def write(x: Int, out: DataOutputStream) = out.writeInt(x) + def read(in: DataInputStream) = in.readInt() + } + + implicit val StringSerializable: Serializable[String] = + new Serializable[String] { + def write(x: String, out: DataOutputStream) = out.writeUTF(x) + def read(in: DataInputStream) = in.readUTF() + } + + def RecSerializable[T, U](implicit + ev1: T unfolds U, + ev2: Serializable[U] + ): Serializable[T] = + new Serializable[T] { + def write(x: T, out: DataOutputStream) = ev2.write(ev1.toShape(x), out) + def read(in: DataInputStream) = ev1.fromShape(ev2.read(in)) + } + + implicit def ShapedSerializable[T, U](implicit + ev1: T shaped U, + ev2: Serializable[U] + ): Serializable[T] = + new Serializable[T] { + def write(x: T, out: DataOutputStream) = ev2.write(ev1.toShape(x), out) + def read(in: DataInputStream) = ev1.fromShape(ev2.read(in)) + } + + implicit def SumSerializable[T, U](implicit + // parameters need to be call by name, or we get a recursive lazy val definition in materialized code + ev1: => Serializable[T], + ev2: => Serializable[U] + ): Serializable[Sum[T, U]] = + new Serializable[Sum[T, U]] { + def write(x: Sum[T, U], out: DataOutputStream): Unit = x match { + case Fst(y) => out.writeBoolean(false); ev1.write(y, out) + case Snd(y) => out.writeBoolean(true); ev2.write(y, out) + } + def read(in: DataInputStream) = in.readBoolean() match { + case false => Fst(ev1.read(in)) + case true => Snd(ev2.read(in)) + } + } + + implicit def ProdSerializable[T, U](implicit + ev1: Serializable[T], + ev2: Serializable[U] + ): Serializable[Prod[T, U]] = + new Serializable[Prod[T, U]] { + def write(x: Prod[T, U], out: DataOutputStream): Unit = { + ev1.write(x.fst, out) + ev2.write(x.snd, out) + } + def read(in: DataInputStream) = { + Prod(ev1.read(in), ev2.read(in)) + } + } + + implicit def IterableSerializable[I[X] <: Iterable[X], Elem](implicit + ev1: GenericCompanion[I], + ev2: Serializable[Elem] + ): Serializable[I[Elem]] = + new Serializable[I[Elem]] { + def write(xs: I[Elem], out: DataOutputStream) = { + out.writeInt(xs.size) + xs.foreach(ev2.write(_, out)) + } + def read(in: DataInputStream) = { + val bldr = ev1.newBuilder[Elem] + for (i <- 0 until in.readInt()) bldr += ev2.read(in) + bldr.result() + } + } +} diff --git a/tests/run/generic/Shapes.scala b/tests/run/generic/Shapes.scala new file mode 100644 index 000000000..8304551e3 --- /dev/null +++ b/tests/run/generic/Shapes.scala @@ -0,0 +1,22 @@ +package generic + +object Shapes { + + trait Sum[+S1, +S2] + case class Fst[+F](x: F) extends Sum[F, Nothing] + case class Snd[+S](x: S) extends Sum[Nothing, S] + + case class Prod[+P1, +P2](fst: P1, snd: P2) + + case class Singleton[SI](value: SI) + + case class EnumValue[E](tag: Int) + + trait shaped[SH1, SH2] extends unfolds[SH1, SH2] + + trait unfolds[UN1, UN2] { + def toShape(x: UN1): UN2 + def fromShape(x: UN2): UN1 + } +} + diff --git a/tests/run/generic/Test.scala b/tests/run/generic/Test.scala new file mode 100644 index 000000000..1431d1185 --- /dev/null +++ b/tests/run/generic/Test.scala @@ -0,0 +1,58 @@ +import generic._ +import Tree._ +import List._ +import java.io._ +import Shapes._ + +object Test { + import Serialization._ + + private var lCount, tCount = 0 + +// ------- Code that will eventually be produced by macros ------------- + + implicit def ListSerializable[Elem](implicit es: Serializable[Elem]): Serializable[List[Elem]] = { + implicit lazy val lsElem: Serializable[List[Elem]] = { + lCount += 1 // test code to verify we create bounded number of Serializables + RecSerializable[List[Elem], List.Shape[Elem]] + } + lsElem + } + + implicit def TreeSerializable[R]: Serializable[Tree[R]] = { + implicit lazy val tR: Serializable[Tree[R]] = { + tCount += 1 // test code to verify we create bounded number of Serializables + RecSerializable[Tree[R], Tree.Shape[R]] + } + tR + } + implicit lazy val tsInt: Serializable[Tree[Int]] = TreeSerializable[Int] + implicit lazy val tsBoolean: Serializable[Tree[Boolean]] = TreeSerializable[Boolean] + +// ------- Test code -------------------------------------------------------- + + /** Serialize data, then deserialize it back and check that it is the same. */ + def sds[D](data: D)(implicit ser: Serializable[D]) = { + val outBytes = new ByteArrayOutputStream + val out = new DataOutputStream(outBytes) + ser.write(data, out) + out.flush() + val inBytes = new ByteArrayInputStream(outBytes.toByteArray) + val in = new DataInputStream(inBytes) + val result = ser.read(in) + assert(data == result, s"$data != $result") + } + + val data1 = + Cons(1, Cons(2, Cons(3, Nil))) + + val data2 = + If(IsZero(Pred(Succ(Zero))), Succ(Succ(Zero)), Pred(Pred(Zero))) + + def main(args: Array[String]) = { + sds(data1) + assert(lCount == 1, lCount) + sds(data2) + assert(tCount == 2, tCount) + } +} \ No newline at end of file diff --git a/tests/run/generic/Tree.scala b/tests/run/generic/Tree.scala new file mode 100644 index 000000000..f4e706944 --- /dev/null +++ b/tests/run/generic/Tree.scala @@ -0,0 +1,113 @@ +package generic + +import Shapes._ + +/** enum Tree[TS] { + * case True extends Tree[Boolean] + * case False extends Tree[Boolean] + * case Zero extends Tree[Int] + * case Succ(n: Tree[Int]) extends Tree[Int] + * case Pred(n: Tree[Int]) extends Tree[Int] + * case IsZero(n: Tree[Int]) extends Tree[Boolean] + * case If(cond: Boolean, thenp: Tree[T], elsep: Tree[T]) extends Tree[T] + * } + */ +sealed trait Tree[TR] extends Enum + +object Tree { + + val True: Tree[Boolean] = new Tree[Boolean] { + def enumTag = 0 + override def toString = "True" + } + implicit def TrueSingleton: Singleton[True.type] = new Singleton[True.type](True) + + val False: Tree[Boolean] = new Tree[Boolean] { + def enumTag = 1 + override def toString = "False" + } + implicit def FalseSingleton: Singleton[False.type] = new Singleton[False.type](False) + + val Zero: Tree[Int] = new Tree[Int] { + def enumTag = 2 + override def toString = "Zero" + } + implicit def ZeroSingleton: Singleton[Zero.type] = new Singleton[Zero.type](Zero) + + abstract case class Succ(n: Tree[Int]) extends Tree[Int] { + def enumTag = 3 + } + object Succ { + def apply(x: Tree[Int]): Tree[Int] = new Succ(x) {} + implicit def SuccShape: Succ `shaped` Tree[Int] = new (Succ `shaped` Tree[Int]) { + def toShape(x: Succ) = x.n + def fromShape(x: Tree[Int]) = new Succ(x) {} + } + } + + abstract case class Pred(n: Tree[Int]) extends Tree[Int] { + def enumTag = 4 + } + object Pred { + def apply(x: Tree[Int]): Tree[Int] = new Pred(x) {} + implicit def PredShape: Pred `shaped` Tree[Int] = new (Pred `shaped` Tree[Int]) { + def toShape(x: Pred) = x.n + def fromShape(x: Tree[Int]) = new Pred(x) {} + } + } + + abstract case class IsZero(n: Tree[Int]) extends Tree[Boolean] { + def enumTag = 5 + } + object IsZero { + def apply(x: Tree[Int]): Tree[Boolean] = new IsZero(x) {} + implicit def IsZeroShape: IsZero `shaped` Tree[Int] = new (IsZero `shaped` Tree[Int]) { + def toShape(x: IsZero) = x.n + def fromShape(x: Tree[Int]) = new IsZero(x) {} + } + } + + abstract case class If[T](cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) extends Tree[T] { + def enumTag = 6 + } + object If { + def apply[T](cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]): Tree[T] = new If(cond, thenp, elsep) {} + type Shape[T] = Prod[Tree[Boolean], Prod[Tree[T], Tree[T]]] + implicit def IfShape[T]: If[T] `shaped` Shape[T] = + new (If[T] `shaped` Shape[T]) { + def toShape(x: If[T]) = Prod(x.cond, Prod(x.thenp, x.elsep)) + def fromShape(x: Shape[T]) = new If(x.fst, x.snd.fst, x.snd.snd) {} + } + } + + type Shape[T] = + Sum[ + Sum[ + Sum[True.type, False.type], + Sum[Zero.type, Succ]], + Sum[ + Sum[Pred, IsZero], + If[T]]] + + implicit def TreeShape[TS]: Tree[TS] `unfolds` Shape[TS] + = new (Tree[TS] `shaped` Shape[TS]) { + def toShape(x: Tree[TS]) = x match { + case True => Fst(Fst(Fst(True))) + case False => Fst(Fst(Snd(False))) + case Zero => Fst(Snd(Fst(Zero))) + case x: Succ => Fst(Snd(Snd(x))) + case x: Pred => Snd(Fst(Fst(x))) + case x: IsZero => Snd(Fst(Snd(x))) + case x: If[TS] => Snd(Snd(x)) + } + def fromShape(x: Shape[TS]): Tree[TS] = x match { + case Fst(Fst(Fst(_true))) => _true.asInstanceOf[Tree[TS]] + case Fst(Fst(Snd(_false))) => _false.asInstanceOf[Tree[TS]] + case Fst(Snd(Fst(zero))) => zero.asInstanceOf[Tree[TS]] + case Fst(Snd(Snd(succ))) => succ.asInstanceOf[Tree[TS]] + case Snd(Fst(Fst(pred))) => pred.asInstanceOf[Tree[TS]] + case Snd(Fst(Snd(isZero))) => isZero.asInstanceOf[Tree[TS]] + case Snd(Snd(_if)) => _if + } + } +} \ No newline at end of file -- cgit v1.2.3