From 971b233bc916ddbb4501f480500f9531fab99f78 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jan 2017 11:20:09 +1100 Subject: [PATCH 1/6] Avoid recomputation of companionRefs The previous condition for caching companionRefs contained a condition result.companionRefs.forall(implicitScopeCache.contains) which was always false because we cache types in `implicitCodeCache`, not companion refs. The new logic fixes this and does not need a second traversal because it is integrated in `iscopeRefs`. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 592e800486ee..0b1eb1afb401 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -380,7 +380,9 @@ trait ImplicitRunInfo { self: RunInfo => EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete. } else { seen += t - iscope(t).companionRefs + val is = iscope(t) + if (!implicitScopeCache.contains(t)) incomplete += tp + is.companionRefs } } @@ -436,10 +438,8 @@ trait ImplicitRunInfo { self: RunInfo => if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope") else if (canCache && - ((tp eq rootTp) || // first type traversed is always cached - !incomplete.contains(tp) && // other types are cached if they are not incomplete - result.companionRefs.forall( // and all their companion refs are cached - implicitScopeCache.contains))) + ((tp eq rootTp) || // first type traversed is always cached + !incomplete.contains(tp))) // other types are cached if they are not incomplete implicitScopeCache(tp) = result result } From 4086195fe188804ce5a052d7b3c2aee099c425d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:50:08 +1100 Subject: [PATCH 2/6] Allow implicit-by-name parameters --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 8 ++------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f62093db00fc..a733eb65da54 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1765,12 +1765,8 @@ object Parsers { TypeTree() // XX-METHOD-INFER } else { accept(COLON) - if (in.token == ARROW) { - if (owner.isTypeName && !(mods is Local)) - syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name") - else if (imods.hasFlags) - syntaxError("implicit parameters may not be call-by-name") - } + if (in.token == ARROW && owner.isTypeName && !(mods is Local)) + syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name") paramType() } val default = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 59df98a93b73..18ae790b7bda 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1879,7 +1879,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val args = (wtp.paramNames, wtp.paramTypes).zipped map { (pname, formal) => def implicitArgError(msg: String => String) = errors += (() => msg(em"parameter $pname of $methodStr")) - inferImplicitArg(formal, implicitArgError, tree.pos.endPos) + if (errors.nonEmpty) EmptyTree + else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos) } if (errors.nonEmpty) { // If there are several arguments, some arguments might already From 269be04ce9f42d57dbc9ceb24afbae1b615e64e4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:51:53 +1100 Subject: [PATCH 3/6] Implement SearchResult.show --- .../tools/dotc/printing/PlainPrinter.scala | 18 ++++++++++++++++++ .../dotty/tools/dotc/printing/Printer.scala | 6 +++++- .../src/dotty/tools/dotc/typer/Implicits.scala | 12 ++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 61f23c21413a..ac25f7cfdf4b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -6,6 +6,7 @@ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, De import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation import StdNames.{nme, tpnme} import ast.Trees._, ast._ +import typer.Implicits._ import config.Config import java.lang.Integer.toOctalString import config.Config.summarizeDepth @@ -484,6 +485,23 @@ class PlainPrinter(_ctx: Context) extends Printer { } }.close // todo: override in refined printer + def toText(result: SearchResult): Text = result match { + case result: SearchSuccess => + "SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree) + case result: NonMatchingImplicit => + "NoImplicitMatches" + case result: DivergingImplicit => + "Diverging Implicit" + case result: ShadowedImplicit => + "Shadowed Implicit" + case result: FailedImplicit => + "Failed Implicit" + case result: AmbiguousImplicits => + "Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2) + case _ => + "?Unknown Implicit Result?" + } + private var maxSummarized = Int.MaxValue def summarized[T](depth: Int)(op: => T): T = { diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 14b63012eb67..506773a4b600 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -5,6 +5,7 @@ import core._ import Texts._, ast.Trees._ import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant, Names.Name, Denotations._, Annotations.Annotation +import typer.Implicits.SearchResult /** The base class of all printers */ @@ -94,7 +95,10 @@ abstract class Printer { /** Textual representation of tree */ def toText[T >: Untyped](tree: Tree[T]): Text - /** Perform string or text-producing operation `op` so that only a + /** Textual representation of implicit search result */ + def toText(result: SearchResult): Text + + /** Perform string or text-producing operation `op` so that only a * summarized text with given recursion depth is shown */ def summarized[T](depth: Int)(op: => T): T diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0b1eb1afb401..183dff2829d2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -6,7 +6,8 @@ import core._ import ast.{Trees, untpd, tpd, TreeInfo} import util.Positions._ import util.Stats.{track, record, monitored} -import printing.Showable +import printing.{Showable, Printer} +import printing.Texts._ import Contexts._ import Types._ import Flags._ @@ -219,14 +220,16 @@ object Implicits { } /** The result of an implicit search */ - abstract class SearchResult + abstract class SearchResult extends Showable { + def toText(printer: Printer): Text = printer.toText(this) + } /** A successful search * @param ref The implicit reference that succeeded * @param tree The typed tree that needs to be inserted * @param ctx The context after the implicit search */ - case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult { + case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult with Showable { override def toString = s"SearchSuccess($tree, $ref, $level)" } @@ -256,7 +259,7 @@ object Implicits { } /** An ambiguous implicits failure */ - class AmbiguousImplicits(alt1: TermRef, alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + class AmbiguousImplicits(val alt1: TermRef, val alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { def explanation(implicit ctx: Context): String = em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify" override def postscript(implicit ctx: Context) = @@ -604,6 +607,7 @@ trait Implicits { self: Typer => result match { case result: SearchSuccess => result.tstate.commit() + implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}") result case result: AmbiguousImplicits => val deepPt = pt.deepenProto From bc06d17ebfd7c8d6003dffe925c3cf9ebca6b1b9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:53:54 +1100 Subject: [PATCH 4/6] Print typerstate nesting info as part of constr printing When printing info about adding to constraints, show the hashes of the typerstate stack, so that we know where constraints are added. --- .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 10 +++++----- compiler/src/dotty/tools/dotc/core/TyperState.scala | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 42df53fed980..3aa20f15b29e 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -79,12 +79,12 @@ trait ConstraintHandling { if (Config.failOnInstantiationToNothing) assert(false, msg) else ctx.log(msg) } - constr.println(i"adding $description") + constr.println(i"adding $description in ${ctx.typerState.hashesStr}") val lower = constraint.lower(param) val res = addOneBound(param, bound, isUpper = true) && lower.forall(addOneBound(_, bound, isUpper = true)) - constr.println(i"added $description = $res") + constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") res } @@ -95,7 +95,7 @@ trait ConstraintHandling { val res = addOneBound(param, bound, isUpper = false) && upper.forall(addOneBound(_, bound, isUpper = false)) - constr.println(i"added $description = $res") + constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") res } @@ -108,12 +108,12 @@ trait ConstraintHandling { val up2 = p2 :: constraint.exclusiveUpper(p2, p1) val lo1 = constraint.nonParamBounds(p1).lo val hi2 = constraint.nonParamBounds(p2).hi - constr.println(i"adding $description down1 = $down1, up2 = $up2") + constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}") constraint = constraint.addLess(p1, p2) down1.forall(addOneBound(_, hi2, isUpper = true)) && up2.forall(addOneBound(_, lo1, isUpper = false)) } - constr.println(i"added $description = $res") + constr.println(i"added $description = $res ${ctx.typerState.hashesStr}") res } diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 5c476c1cbc6e..206438d8656b 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -79,6 +79,9 @@ class TyperState(r: Reporter) extends DotClass with Showable { def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = unsupported("tryWithFallBack") override def toText(printer: Printer): Text = "ImmutableTyperState" + + /** A string showing the hashes of all nested mutable typerstates */ + def hashesStr: String = "" } class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean) @@ -207,4 +210,7 @@ extends TyperState(r) { } override def toText(printer: Printer): Text = constraint.toText(printer) + + override def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr + } From fe09e0d8fe68e0b48d5e864e1de12ae5ee86077d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jan 2017 14:46:24 +1100 Subject: [PATCH 5/6] 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 diff --git a/tests/run/generic/Color.scala b/tests/run/generic/Color.scala new file mode 100644 index 000000000000..ed248295d834 --- /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 000000000000..dbdbfe8ebe02 --- /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 000000000000..3f365765698c --- /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 000000000000..be8ebd15dcb5 --- /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 000000000000..a82d6bc7a100 --- /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 000000000000..8304551e3287 --- /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 000000000000..1431d1185acd --- /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 000000000000..f4e7069448a3 --- /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 From 56d32fa7bd548870c8382824fe748c5c5fde1d27 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jan 2017 17:15:13 +1100 Subject: [PATCH 6/6] Add to test case --- tests/run/generic/SearchResult.scala | 9 +++++---- tests/run/generic/Test.scala | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/run/generic/SearchResult.scala b/tests/run/generic/SearchResult.scala index be8ebd15dcb5..1c86d1b4f0eb 100644 --- a/tests/run/generic/SearchResult.scala +++ b/tests/run/generic/SearchResult.scala @@ -11,7 +11,7 @@ import Shapes._ */ sealed trait SearchResult extends Enum -object SearchResult extends EnumValues[SearchResult](2) { +object SearchResult extends EnumValues[SearchResult](3) { private def $new(tag: Int, name: String) = new SearchResult { def enumTag = tag @@ -46,9 +46,10 @@ object SearchResult extends EnumValues[SearchResult](2) { } } - implicit def SearchResultShape: - SearchResult `shaped` Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]] = - new (SearchResult `shaped` Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]]) { + type Shape = Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]] + + implicit def SearchResultShape: SearchResult `unfolds` Shape = + new (SearchResult `shaped` Shape) { def toShape(x: SearchResult) = x match { case x: Success => Fst(x) case x: Ambiguous => Snd(Fst(x)) diff --git a/tests/run/generic/Test.scala b/tests/run/generic/Test.scala index 1431d1185acd..ac0dc4d5aae3 100644 --- a/tests/run/generic/Test.scala +++ b/tests/run/generic/Test.scala @@ -3,11 +3,12 @@ import Tree._ import List._ import java.io._ import Shapes._ +import SearchResult._ object Test { import Serialization._ - private var lCount, tCount = 0 + private var lCount, tCount, sCount = 0 // ------- Code that will eventually be produced by macros ------------- @@ -29,6 +30,11 @@ object Test { implicit lazy val tsInt: Serializable[Tree[Int]] = TreeSerializable[Int] implicit lazy val tsBoolean: Serializable[Tree[Boolean]] = TreeSerializable[Boolean] + implicit lazy val SearchResultSerializable: Serializable[SearchResult] = { + sCount += 1 + RecSerializable[SearchResult, SearchResult.Shape] + } + // ------- Test code -------------------------------------------------------- /** Serialize data, then deserialize it back and check that it is the same. */ @@ -49,10 +55,18 @@ object Test { val data2 = If(IsZero(Pred(Succ(Zero))), Succ(Succ(Zero)), Pred(Pred(Zero))) + val data3 = Cons(Color.Red, Cons(Color.Green, Cons(Color.Blue, Nil))) + + val data4 = Ambiguous(Success(Color.Green), Diverging) + def main(args: Array[String]) = { sds(data1) assert(lCount == 1, lCount) sds(data2) assert(tCount == 2, tCount) + sds(data3) + assert(lCount == 2, lCount) + sds(data4) + assert(sCount == 1, sCount) } } \ No newline at end of file