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 + } 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/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 592e800486ee..f7d8556a780a 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) = @@ -380,7 +383,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 +441,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 } @@ -504,7 +507,36 @@ trait Implicits { self: Typer => * which is itself parameterized by another string, * indicating where the implicit parameter is needed */ - def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = + def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = { + + /** If `formal` is of the form ClassTag[T], where `T` is a class type, + * synthesize a class tag for `T`. + */ + def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = { + if (formal.isRef(defn.ClassTagClass)) + formal.argTypes match { + case arg :: Nil => + fullyDefinedType(arg, "ClassTag argument", pos) match { + case defn.ArrayOf(elemTp) => + val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos) + if (etag.isEmpty) etag else etag.select(nme.wrap) + case tp if hasStableErasure(tp) => + if (defn.isBottomClass(tp.typeSymbol)) + error(where => i"attempt to take ClassTag of undetermined type for $where") + ref(defn.ClassTagModule) + .select(nme.apply) + .appliedToType(tp) + .appliedTo(clsOf(erasure(tp))) + .withPos(pos) + case tp => + EmptyTree + } + case _ => + EmptyTree + } + else EmptyTree + } + inferImplicit(formal, EmptyTree, pos) match { case SearchSuccess(arg, _, _, _) => arg @@ -531,24 +563,6 @@ trait Implicits { self: Typer => EmptyTree } } - - /** If `formal` is of the form ClassTag[T], where `T` is a class type, - * synthesize a class tag for `T`. - */ - def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = { - if (formal.isRef(defn.ClassTagClass)) - formal.argTypes match { - case arg :: Nil => - val tp = fullyDefinedType(arg, "ClassTag argument", pos) - if (hasStableErasure(tp)) - return ref(defn.ClassTagModule) - .select(nme.apply) - .appliedToType(tp) - .appliedTo(clsOf(erasure(tp))) - .withPos(pos) - case _ => - } - EmptyTree } private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { @@ -604,6 +618,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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d05a0aaa727d..22a4221ba1ed 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1880,7 +1880,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 diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala index eaa0bea84fbe..5d16917cc368 100644 --- a/compiler/test/dotty/tools/dotc/CompilerTest.scala +++ b/compiler/test/dotty/tools/dotc/CompilerTest.scala @@ -397,20 +397,25 @@ abstract class CompilerTest { /** Gives an error message for one line where the expected number of errors and * the number of compiler errors differ. */ def compareLines(fileName: String, expectedLines: List[(Int, Int)], foundLines: List[(Int, Int)]) = { - expectedLines.foreach({ case (line, expNr) => - foundLines.find(_._1 == line) match { - case Some((_, `expNr`)) => // this line is ok - case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr) - case None => errorMsg(fileName, Some(line), expNr, 0) - } - }) - foundLines.foreach({ case (line, foundNr) => - expectedLines.find(_._1 == line) match { - case Some((_, `foundNr`)) => // this line is ok - case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr) - case None => errorMsg(fileName, Some(line), 0, foundNr) - } - }) + expectedLines foreach{ + case (line, expNr) => + foundLines.find(_._1 == line) match { + case Some((_, `expNr`)) => // this line is ok + case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr) + case None => + println(s"expected lines = $expectedLines%, %") + println(s"found lines = $foundLines%, %") + errorMsg(fileName, Some(line), expNr, 0) + } + } + foundLines foreach { + case (line, foundNr) => + expectedLines.find(_._1 == line) match { + case Some((_, `foundNr`)) => // this line is ok + case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr) + case None => errorMsg(fileName, Some(line), 0, foundNr) + } + } } // ========== PARTEST HELPERS ============= diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index cd90c4882b77..c7cf2a9061f5 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -9,9 +9,6 @@ import scala.collection.Seq object DottyPredef { implicit def typeTag[T]: TypeTag[T] = ??? - implicit def arrayTag[T](implicit ctag: ClassTag[T]): ClassTag[Array[T]] = - ctag.wrap - /** A fall-back implicit to compare values of any types. * The compiler will restrict implicit instances of `eqAny`. An instance * `eqAny[T, U]` is _valid_ if `T <: U` or `U <: T` or both `T` and `U` are diff --git a/tests/neg/i1802.scala b/tests/neg/i1802.scala index 56da672a80b8..93e790f18dc5 100644 --- a/tests/neg/i1802.scala +++ b/tests/neg/i1802.scala @@ -14,8 +14,8 @@ object Exception { def apply(x: Throwable): T = f(downcast(x).get) } - def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) + def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) // error: undetermined ClassTag - implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = // error: cyclic reference + implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = mkCatcher(pf.isDefinedAt _, pf.apply _) } diff --git a/tests/neg/i1907.scala b/tests/neg/i1907.scala new file mode 100644 index 000000000000..6bc3bb56f7c4 --- /dev/null +++ b/tests/neg/i1907.scala @@ -0,0 +1,7 @@ +import java.io.File + +object Test { + Some(new File(".")) + .map(_.listFiles).getOrElse(Array.empty) // error: undetermined ClassTag + .map(_.listFiles) +} diff --git a/tests/neg/undet-classtag.scala b/tests/neg/undet-classtag.scala new file mode 100644 index 000000000000..dfe0eb2db8b0 --- /dev/null +++ b/tests/neg/undet-classtag.scala @@ -0,0 +1,27 @@ +import scala.reflect.ClassTag + +object Test { + def f[T: reflect.ClassTag](x: T) = ??? + + f(???) // error: undetermined ClassTag +} + +// SI 9754 +object Program { + def test[T: ClassTag](x: T) = { + val arr = new Array[T](1) + println(arr.getClass) + println(x.getClass) + arr(0) = x + } + + def main(args: Array[String]): Unit = { + test(new Array[Nothing](0)) // error: undetermined ClassTag + } +} + +// SI 5353 +object t5353 { + if (false) Array("qwe") else Array() // error: undetermined ClassTag +} + diff --git a/tests/pos/t3859.scala b/tests/pos/t3859.scala index 9922073016bf..486c1d4b2b36 100644 --- a/tests/pos/t3859.scala +++ b/tests/pos/t3859.scala @@ -1,4 +1,4 @@ class Test { - def foo: Unit = bar(Array(): _*) + def foo: Unit = bar(Array[AnyRef](): _*) def bar(values: AnyRef*): Unit = () } diff --git a/tests/pos/t5859.scala b/tests/pos/t5859.scala index 2a31e68ee53d..60ec8b4cba35 100644 --- a/tests/pos/t5859.scala +++ b/tests/pos/t5859.scala @@ -7,9 +7,9 @@ class A { f(List[AnyRef](): _*) f(List(): _*) f(Nil: _*) - f(Array(): _*) + // f(Array(): _*) // undetermined ClassTag f(Array[AnyRef](): _*) f(List(1)) f(List(1), Nil: _*) - f(List(1), Array(): _*) + // f(List(1), Array(): _*) // undetermined ClassTag } diff --git a/tests/run/array-addition.scala b/tests/run/array-addition.scala index 8def48e85cd4..09a1b0badace 100644 --- a/tests/run/array-addition.scala +++ b/tests/run/array-addition.scala @@ -4,8 +4,8 @@ object Test { def main(args: Array[String]): Unit = { prettyPrintArray(Array(1,2,3) :+ 4) prettyPrintArray(1 +: Array(2,3,4)) - prettyPrintArray(Array() :+ 1) - prettyPrintArray(1 +: Array()) + prettyPrintArray(Array[Int]() :+ 1) + prettyPrintArray(1 +: Array[Int]()) } } 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..1c86d1b4f0eb --- /dev/null +++ b/tests/run/generic/SearchResult.scala @@ -0,0 +1,64 @@ +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](3) { + + 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) {} + } + } + + 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)) + 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..ac0dc4d5aae3 --- /dev/null +++ b/tests/run/generic/Test.scala @@ -0,0 +1,72 @@ +import generic._ +import Tree._ +import List._ +import java.io._ +import Shapes._ +import SearchResult._ + +object Test { + import Serialization._ + + private var lCount, tCount, sCount = 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] + + 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. */ + 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))) + + 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 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