From 30f3ea1710203b7ccd004bdee414389342d589dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jan 2017 11:20:09 +1100 Subject: 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 592e80048..0b1eb1afb 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 } -- cgit v1.2.3 From 801554c386253ad8474babb2d40590c2c8d3a45c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:50:08 +1100 Subject: 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 f62093db0..a733eb65d 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 d05a0aaa7..22a4221ba 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 -- cgit v1.2.3 From dd0a8032f70cf7c15a928d5756ce8f27791c9a7d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:51:53 +1100 Subject: Implement SearchResult.show --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 18 ++++++++++++++++++ compiler/src/dotty/tools/dotc/printing/Printer.scala | 6 +++++- compiler/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 61f23c214..ac25f7cfd 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 14b63012e..506773a4b 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 0b1eb1afb..183dff282 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 -- cgit v1.2.3 From fc2bbfeaaf50c073ff1f22bfd0f3231a0b8d08a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:53:54 +1100 Subject: 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. --- compiler/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 42df53fed..3aa20f15b 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 5c476c1cb..206438d86 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 + } -- cgit v1.2.3 From 2294e82f6071dedd43b41af6063da5519fd70884 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 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 From 2ce93287d809ed0012546c8466712a40e5a9c2e5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 31 Jan 2017 17:15:13 +1100 Subject: 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 be8ebd15d..1c86d1b4f 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 1431d1185..ac0dc4d5a 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 -- cgit v1.2.3 From b11e6d678a92187e5e9f821ba1116cec2cce0f8c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Feb 2017 16:43:00 +1100 Subject: Handle Array classtags in the same way as others The previous implicit definition of arrayTag in DottyPredef priorities arrayTag over all other classtag searches, which led to surprising results in `i1907a.scala`. --- .../src/dotty/tools/dotc/typer/Implicits.scala | 47 +++++++++++++--------- library/src/dotty/DottyPredef.scala | 3 -- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 183dff282..2112b1221 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -507,7 +507,34 @@ 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) => + 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 @@ -534,24 +561,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) = { diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index cd90c4882..c7cf2a906 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 -- cgit v1.2.3 From e34555f69e7cdd6d19d0d1ed969127f4ee65c36e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Feb 2017 17:18:13 +1100 Subject: Disallow taking a class tag of Nothing or Null. It seems in most cases this leads to weird behavior and cause confusing error messages later. It also means we cannot create an Array[Nothing], except by passing the classtag explicitly. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 ++ tests/neg/i1802.scala | 4 ++-- tests/neg/i1907.scala | 7 +++++++ tests/neg/undet-classtag.scala | 5 +++++ tests/pos/t3859.scala | 2 +- tests/pos/t5859.scala | 4 ++-- tests/run/array-addition.scala | 4 ++-- 7 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 tests/neg/i1907.scala create mode 100644 tests/neg/undet-classtag.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 2112b1221..f7d8556a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -521,6 +521,8 @@ trait Implicits { self: Typer => 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) diff --git a/tests/neg/i1802.scala b/tests/neg/i1802.scala index 56da672a8..93e790f18 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 000000000..6bc3bb56f --- /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 000000000..563596d14 --- /dev/null +++ b/tests/neg/undet-classtag.scala @@ -0,0 +1,5 @@ +object Test { + def f[T: reflect.ClassTag](x: T) = ??? + + f(???) // error: undetermined ClassTag +} diff --git a/tests/pos/t3859.scala b/tests/pos/t3859.scala index 992207301..486c1d4b2 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 2a31e68ee..60ec8b4cb 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 8def48e85..09a1b0bad 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]()) } } -- cgit v1.2.3 From 029c6aa106c5a8e91d2f75421ce20ee87ff1b8f8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Feb 2017 17:48:58 +1100 Subject: Improve formatting --- compiler/test/dotty/tools/dotc/CompilerTest.scala | 33 +++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala index eaa0bea84..5d16917cc 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 ============= -- cgit v1.2.3 From abbee9e28ef3f0150c9afa48f485ecc49e0e3787 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Feb 2017 18:43:04 +1100 Subject: Update test file Updated with SI issues reported by Jason --- tests/neg/undet-classtag.scala | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/neg/undet-classtag.scala b/tests/neg/undet-classtag.scala index 563596d14..dfe0eb2db 100644 --- a/tests/neg/undet-classtag.scala +++ b/tests/neg/undet-classtag.scala @@ -1,5 +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 +} + -- cgit v1.2.3