From 609acf30b2999869098c39c092d8728e841b836d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 May 2018 10:30:35 +0200 Subject: [PATCH 01/18] Fix: Preserve mode when lazy unpickling In readLater the context is not preserved, but reconstituted using the owner of the context building the closure. We need to pass the mode as well, or else ReadPositions might be reset. I noticed this when I saw that positions were not read correctly when inlining. --- .../src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 44635eb5a249..617ff9c1e951 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1135,7 +1135,7 @@ class TreeUnpickler(reader: TastyReader, def readLater[T <: AnyRef](end: Addr, op: TreeReader => Context => T)(implicit ctx: Context): Trees.Lazy[T] = { val localReader = fork goto(end) - new LazyReader(localReader, ctx.owner, op) + new LazyReader(localReader, ctx.owner, ctx.mode, op) } def readHole(end: Addr, isType: Boolean)(implicit ctx: Context): Tree = { @@ -1182,10 +1182,10 @@ class TreeUnpickler(reader: TastyReader, } } - class LazyReader[T <: AnyRef](reader: TreeReader, owner: Symbol, op: TreeReader => Context => T) extends Trees.Lazy[T] { + class LazyReader[T <: AnyRef](reader: TreeReader, owner: Symbol, mode: Mode, op: TreeReader => Context => T) extends Trees.Lazy[T] { def complete(implicit ctx: Context): T = { pickling.println(i"starting to read at ${reader.reader.currentAddr} with owner $owner") - op(reader)(ctx.withPhaseNoLater(ctx.picklerPhase).withOwner(owner)) + op(reader)(ctx.withPhaseNoLater(ctx.picklerPhase).withOwner(owner).withModeBits(mode)) } } From bf4d3b09d1f38868b47a6457ceafd98c2bf9e266 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 May 2018 17:54:44 +0200 Subject: [PATCH 02/18] Retract InSuperCall mode when unpickling the right-hand sides of definitions This was done by accident before, because we did not keep the mode bits of the context calling a readLater. --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 617ff9c1e951..e9fbd6fc5278 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -707,7 +707,7 @@ class TreeUnpickler(reader: TastyReader, def readRhs(implicit ctx: Context) = if (noRhs(end)) EmptyTree - else readLater(end, rdr => ctx => rdr.readTerm()(ctx)) + else readLater(end, rdr => ctx => rdr.readTerm()(ctx.retractMode(Mode.InSuperCall))) def ValDef(tpt: Tree) = ta.assignType(untpd.ValDef(sym.name.asTermName, tpt, readRhs(localCtx)), sym) From f9c2e54795e0256ab8f48cecd48364f488337b1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 May 2018 10:31:33 +0200 Subject: [PATCH 03/18] Fix: Don't generate inline accessors for pattern bound variables --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 49a507e0994c..a995259550dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -53,6 +53,7 @@ object Inliner { * by excluding all symbols properly contained in the inlined method. */ def needsAccessor(sym: Symbol)(implicit ctx: Context) = + sym.isTerm && (sym.is(AccessFlags) || sym.privateWithin.exists) && !sym.owner.isContainedIn(inlineMethod) From c0eb027432d0506b00e481832c3e9f672e548c73 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 May 2018 11:07:33 +0200 Subject: [PATCH 04/18] Fix: printing modifiers When printing trees, modifiers were always taken from the tree. This is wrong for typed trees, since their flags and annotations come from the symbol, the tree modifiers are no longer relevant. --- .../dotc/printing/DecompilerPrinter.scala | 4 +-- .../tools/dotc/printing/RefinedPrinter.scala | 28 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index 288d07c30ff3..6b0fd7c47d22 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -49,8 +49,8 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def templateText(tree: TypeDef, impl: Template): Text = { val decl = - if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) - else modText(tree.mods &~ (Final | Module), keywordStr("object")) + if (!tree.mods.is(Module)) modText(tree.mods, tree.symbol, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + else modText(tree.mods, tree.symbol, keywordStr("object"), suppress = Final | Module) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ "" } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6500693da068..b900f041c436 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -411,7 +411,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree @ TypeDef(name, rhs) => def typeDefText(tparamsText: => Text, rhsText: => Text) = dclTextOr(tree) { - modText(tree.mods, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ + modText(tree.mods, tree.symbol, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { @@ -449,7 +449,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { - modText(tree.mods, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) + modText(tree.mods, NoSymbol, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) } case SymbolLit(str) => "'" + str @@ -506,7 +506,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { t ~ cxBoundToText(cxb) } case PatDef(mods, pats, tpt, rhs) => - modText(mods, keywordStr("val")) ~~ toText(pats, ", ") ~ optAscription(tpt) ~ + modText(mods, NoSymbol, keywordStr("val")) ~~ toText(pats, ", ") ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) case ParsedTry(expr, handler, finalizer) => changePrec(GlobalPrec) { @@ -618,7 +618,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ + modText(tree.mods, tree.symbol, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } } @@ -627,7 +627,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + val prefix = modText(tree.mods, tree.symbol, keywordStr("def")) ~~ valDefText(nameIdText(tree)) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ optText(tree.rhs)(" = " ~ _) @@ -642,7 +642,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val prefix: Text = if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt else { - var modsText = modText(constr.mods, "") + var modsText = modText(constr.mods, constr.symbol, "") if (!modsText.isEmpty) modsText = " " ~ modsText if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } @@ -670,7 +670,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def templateText(tree: TypeDef, impl: Template): Text = { - val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + val decl = modText(tree.mods, tree.symbol, keywordStr(if ((tree).mods is Trait) "trait" else "class")) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") } @@ -693,16 +693,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + protected def modText(mods: untpd.Modifiers, sym: Symbol, kw: String, suppress: FlagSet = EmptyFlags): Text = { // DD val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param var flagMask = if (ctx.settings.YdebugFlags.value) AnyFlags - else if (suppressKw) PrintableFlags &~ Private - else PrintableFlags + else if (suppressKw) PrintableFlags &~ Private &~ suppress + else PrintableFlags &~ suppress if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes - val flags = mods.flags & flagMask - val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) - val annotations = filterModTextAnnots(mods.annotations) + val flags = (if (sym.exists) sym.flags else (mods.flags)) & flagMask + val flagsText = if (flags.isEmpty) "" else keywordStr(flags.toString) + val annotations = filterModTextAnnots( + if (sym.exists) sym.annotations.filterNot(_.isInstanceOf[Annotations.BodyAnnotation]).map(_.tree) + else mods.annotations) Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } From bde27bfa9a704d918853ff47af5abc64f511958b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 May 2018 11:26:11 +0200 Subject: [PATCH 05/18] Fix: Pickle Label flag in TreePickler Was forgotten before, which meant that labels were forgotten in inlined code. The code was still correct, but a method would be created for each label. --- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 746b7baad157..4c4ab3c72147 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -607,6 +607,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is Stable) writeByte(STABLE) if ((flags is ParamAccessor) && sym.isSetter) writeByte(PARAMsetter) + if ((flags is Label)) writeByte(LABEL) } else { if (flags is Sealed) writeByte(SEALED) if (flags is Abstract) writeByte(ABSTRACT) From 2368fb482c52eb823f3b5e94c44c3989d0c226ac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 May 2018 17:14:34 +0200 Subject: [PATCH 06/18] Fix: handle accesses to private aliases from inlined code Dealias them and try again. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 13 +++++++++++ tests/pos/inlineAccesses.scala | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/pos/inlineAccesses.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index a995259550dc..1cb9bf0936ae 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -26,6 +26,7 @@ import ErrorReporting.errorTree import collection.mutable import transform.TypeUtils._ import reporting.trace +import util.Positions.Position object Inliner { import tpd._ @@ -538,6 +539,18 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { var retainedInlineables = Set[Symbol]() + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { + tpe match { + case tpe @ TypeRef(pre, _) if !tpe.symbol.isAccessibleFrom(pre, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case _ => + } + case _ => + } + super.ensureAccessible(tpe, superAccess, pos) + } + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = tree.asInstanceOf[tpd.Tree] match { case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs diff --git a/tests/pos/inlineAccesses.scala b/tests/pos/inlineAccesses.scala new file mode 100644 index 000000000000..0305e0f37168 --- /dev/null +++ b/tests/pos/inlineAccesses.scala @@ -0,0 +1,22 @@ +trait T { + object O +} + +class C { + private type T = C + private var x = 0 + + inline def f = { + x += 1 + x = x + 1 + x + } + inline def dup = new T +} + +object Test { + + val c = new C + c.f + c.dup +} From 717a301d2abb397c7c5d9494e407e956aaa1cbc7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 May 2018 19:22:27 +0200 Subject: [PATCH 07/18] Experiment with specialized lists The idea is to have a compiler-specific list data type that optimizes for small lists. Where the overhead of normal lists is one Cons node per element, the overhead of `Lst` is 0 for lists of length <= 1, and 1 array node for longer lists. Furthermore, we'll try to inline heavily, which is easier for specialized data structures. The main downsides are inefficient `tail` and `::`, in particular for longer lists. Compiler code will need to be adapted to avoid these where it is performance critical. This also serves as test case for the inline related fixes in the previous commits. --- tests/run/lst/Lst.scala | 496 ++++++++++++++++++++++++++++++++++++ tests/run/lst/LstTest.scala | 329 ++++++++++++++++++++++++ 2 files changed, 825 insertions(+) create mode 100644 tests/run/lst/Lst.scala create mode 100644 tests/run/lst/LstTest.scala diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala new file mode 100644 index 000000000000..f501bff72381 --- /dev/null +++ b/tests/run/lst/Lst.scala @@ -0,0 +1,496 @@ +package dotty.tools.dotc +package util + +import printing.{Printer, Texts} +import Texts.Text +import collection.mutable.{ListBuffer, StringBuilder} + + +/** A lightweight class for lists, optimized for short and medium lengths. + * A list is represented at runtime as + * + * If it is empty: the value `Lst.Empty` + * If it contains one element: the element itself + * If it contains more elements: an Array[Any] containing the elements + */ +class Lst[+T](val elems: Any) extends AnyVal { + import Lst._ + + def length: Int = elems match { + case null => 0 + case elems: Arr => elems.length + case elem => 1 + } + + def isEmpty = elems == null + def nonEmpty = elems != null + + inline def foreach(inline op: T => Unit): Unit = { + def sharedOp(x: T) = op(x) + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { sharedOp(elem(i)); i += 1 } + case elem: T @ unchecked => sharedOp(elem) + } + } + + /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. + * Should be used only of `op` is small + */ + inline def foreachInlined(inline op: T => Unit): Unit = elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { op(elem(i)); i += 1 } + case elem: T @unchecked => op(elem) + } + + def iterator(): Iterator[T] = elems match { + case null => Iterator.empty + case elems: Arr => elems.iterator.asInstanceOf[Iterator[T]] + case elem: T @unchecked => Iterator.single(elem) + } + + def copyToArray[U >: T](target: Array[U], from: Int) = elems match { + case null => + case elems: Arr => System.arraycopy(elems, 0, target, from, elems.length) + case elem: T @ unchecked => target(from) = elem + } + + /** `f` is pulled out, not duplicated */ + inline def map[U](inline f: T => U): Lst[U] = { + def op(x: T) = f(x) + elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } + new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](op(elem)) + } + } + + def mapConserve[U](f: T => U): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var newElems: Arr = null + var i = 0 + while (i < elems.length) { + val x = elem(i) + val y = f(x) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(elems.length) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](f(elem)) + } + + def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElemss: Arr = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val ys = f(elem(i)) + len += ys.length + newElemss(i) = ys.elems + i += 1 + } + if (len == 0) Empty + else if (len == 1) { + i = 0 + while (newElemss(i) == null) i += 1 + new Lst[U](newElemss(i)) + } + else { + val newElems = new Arr(len) + i = 0 + var j = 0 + while (i < newElemss.length) { + val ys = new Lst[U](newElemss(i)) + ys.copyToArray(newElems, j) + j += ys.length + i += 1 + } + new Lst[U](newElems) + } + case elem: T @ unchecked => new Lst[U](f(elem).elems) + } + + def filter(p: T => Boolean): Lst[T] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val scratch = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val x = elem(i) + if (p(x)) { scratch(len) = x; len += 1 } + i += 1 + } + if (len == elems.length) this + else _fromArray(scratch, 0, len) + case elem: T @unchecked => + if (p(elem)) this else Empty + } + def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) + + inline def exists(inline p: T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && !op(elem(i))) i += 1 + i < elems.length + case elem: T @unchecked => + op(elem) + } + } + + inline def forall(inline p: T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => true + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && op(elem(i))) i += 1 + i == elems.length + case elem: T @unchecked => + op(elem) + } + } + + inline def contains[U >: T](x: U): Boolean = elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && elem(i) != x) i += 1 + i < elems.length + case elem: T @unchecked => + elem == x + } + + inline def foldLeft[U](z: U)(inline f: (U, T) => U) = { + def op(x: U, y: T) = f(x, y) + elems match { + case null => z + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + var acc = z + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + op(z, elem) + } + } + + inline def /: [U](z: U)(inline op: (U, T) => U) = foldLeft(z)(op) + + def reduceLeft[U >: T](op: (U, U) => U) = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 1 + var acc: U = elem(0) + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + elem + } + + def reverse: Lst[T] = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { + newElems(elems.length - 1 - i) = elem(i) + i += 1 + } + new Lst[T](newElems) + case _ => this + } + + def apply(n: Int): T = elems match { + case null => throw new IndexOutOfBoundsException(n.toString) + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + elem(n) + case elem: T @unchecked => + if (n == 0) elem else throw new IndexOutOfBoundsException(n.toString) + } + + def head: T = apply(0) + def last: T = apply(length - 1) + + def headOr[U >: T](alt: => U): U = if (isEmpty) alt else head + + def slice(start: Int, end: Int): Lst[T] = + if (start < 0) slice(0, end) + else elems match { + case null => this + case elems: Arr => _fromArray(elems, start, end `min` elems.length) + case elem: T @ unchecked => if (end == 0) Empty else this + } + + def drop(n: Int): Lst[T] = slice(n, length) + def tail = drop(1) + def take(n: Int): Lst[T] = slice(0, n) + + def ++ [U >: T](that: Lst[U]): Lst[U] = + if (elems == null) that + else if (that.elems == null) this + else { + val len1 = this.length + val len2 = that.length + val newElems = new Arr(len1 + len2) + this.copyToArray(newElems, 0) + that.copyToArray(newElems, len1) + new Lst[U](newElems) + } + + def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = + this.elems match { + case null => Empty + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length min elems2.length + if (len == 0) Empty + else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) + else { + var newElems: Arr = null + var i = 0 + while (i < len) { + val x = elem1(i) + val y = op(x, elem2(i)) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(len) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + new Lst[V](newElems) + } + case elem2: U @unchecked => + new Lst[V](op(elem1(0), elem2)) + } + case elem1: T @unchecked => + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + new Lst[V](op(elem1, elem2(0))) + case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) + } + } + + def zip[U](that: Lst[U]): Lst[(T, U)] = zipWith(that)((_, _)) + + def zipWithIndex: Lst[(T, Int)] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } + new Lst[(T, Int)](newElems) + case elem: T @unchecked => + new Lst[(T, Int)]((elem, 0)) + } + + def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = + (this `eqLst` that) || { + this.elems match { + case null => + that.elems == null + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && p(elem1(i), elem2(i))) i += 1 + i == len + } + case _ => false + } + case elem1: T @unchecked => + that.elems match { + case null => false + case elems2: Arr => false + case elem2: U @unchecked => p(elem1, elem2) + } + } + } + + def === [U](that: Lst[U]) = + (this `eqLst` that) || { + elems match { + case elems1: Arr => + that.elems match { + case elems2: Arr => + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && elems1(i).equals(elems2(i))) i += 1 + i == len + } + case _ => false + } + case elem => elem == that.elems + } + } + + def eqLst[U](that: Lst[U]) = eq(elems, that.elems) + + def eqElements[U](that: Lst[U]): Boolean = corresponds(that)(eqFn) + + def mkString: String = mkString(", ") + + def mkString(sep: String): String = mkString("", sep, "") + def mkString(left: String, sep: String, right: String): String = { + val b = new StringBuilder(left) + var first = true + foreachInlined { elem => + if (first) first = false else b ++= sep + b ++= elem.toString + } + b ++= right + b.toString + } + + override def toString = mkString("Lst(", ", ", ")") +} + +object Lst { + + private type Arr = Array[Any] + + private def eq(x: Any, y: Any) = x.asInstanceOf[AnyRef] `eq` y.asInstanceOf[AnyRef] + private val eqFn = (x: Any, y: Any) => eq(x, y) + + val Empty = new Lst[Nothing](null) + + def apply[T](): Lst[T] = Empty + + def apply[T](x0: T): Lst[T] = new Lst[T](x0) + + def apply[T](x0: T, x1: T): Lst[T] = { + val elems = new Arr(2) + elems(0) = x0 + elems(1) = x1 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T): Lst[T] = { + val elems = new Arr(3) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T): Lst[T] = { + val elems = new Arr(4) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T, x4: T, xs: T*): Lst[T] = { + val elems = new Arr(5 + xs.length) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + elems(4) = x4 + xs.copyToArray(elems, 5) + new Lst[T](elems) + } + + def fill[T](n: Int)(elem: => T) = { + val elems = new Arr(n) + var i = 0 + while (i < n) { elems(i) = elem; i += 1} + new Lst[T](elems) + } + + class Buffer[T] { + private var len = 0 + private var elem: T = _ + private var elems: Arr = _ + + def size = len + + /** pre: len > 0, n > 1 */ + private def ensureSize(n: Int) = + if (len == 1) { + elems = new Arr(n `max` 16) + elems(0) = elem + } + else if (len < n) { + val newLen = n `max` len << 2 + val newElems = new Arr(newLen) + System.arraycopy(elems, 0, newElems, 0, len) + elems = newElems + } + + def += (x: T): this.type = { + if (len == 0) elem = x + else { + ensureSize(len + 1) + elems(len) = x + } + len += 1 + this + } + + def ++= (xs: Lst[T]): this.type = { + xs.elems match { + case null => this + case elems2: Arr => + if (len == 0) elems = elems2 + else { + ensureSize(len + elems2.length) + System.arraycopy(elems2, 0, elems, len, elems2.length) + } + len += elems2.length + case elem: T @unchecked => this += elem + } + this + } + + def toLst: Lst[T] = + if (len == 0) Empty + else if (len == 1) new Lst[T](elem) + else _fromArray(elems, 0, len) + + def clear() = + len = 0 + } + + private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { + val len = end - start + if (len <= 0) Empty + else if (len == 1) new Lst[T](elems(start)) + else if (start == 0 && end == elems.length) new Lst[T](elems) + else { + val newElems = new Arr(len) + System.arraycopy(elems, start, newElems, 0, len) + new Lst[T](newElems) + } + } + + def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = + _fromArray(elems.asInstanceOf[Arr], start, end) +} diff --git a/tests/run/lst/LstTest.scala b/tests/run/lst/LstTest.scala new file mode 100644 index 000000000000..87f2541fb62a --- /dev/null +++ b/tests/run/lst/LstTest.scala @@ -0,0 +1,329 @@ +object Test extends App { + import dotty.tools.dotc.util.Lst + + val xs0: Lst[String] = Lst.Empty + val xs1 = Lst("a") + val xs2 = Lst("a", "b") + val xs3 = Lst("a", "b", "c") + val xs4 = Lst("a", "b", "c", "d") + val xs5 = Lst("a", "b", "c", "d", "e") + val xs10 = xs5 ++ xs5 + + val is0: Lst[Int] = Lst.Empty + val is1 = Lst(1) + val is2 = Lst(1, 2) + val is3 = Lst(1, 2, 3) + val is4 = Lst(1, 2, 3, 4) + val is5 = Lst(1, 2, 3, 4, 5) + val is10 = is5 ++ is5 + + def lengthTest() = { + assert(xs0.length == 0) + assert(xs1.length == 1) + assert(xs2.length == 2) + assert(xs10.length == 10) + + assert(is0.length == 0) + assert(is1.length == 1) + assert(is2.length == 2) + assert(is10.length == 10) + } + + def concatTest() = { + assert(xs1 ++ xs0 == xs1) + assert(xs0 ++ xs1 == xs1) + assert(xs3 ++ xs0 == xs3) + assert(xs0 ++ xs4 == xs4) + + assert(is1 ++ is0 == is1) + assert(is0 ++ is1 == is1) + assert(is3 ++ is0 == is3) + assert(is0 ++ is4 == is4) + } + + def foreachTest() = { + xs0.foreach(s => assert(s.length == 1)) + xs3.foreach(s => assert(s.length == 1)) + def test1() = { + var x = 0 + xs10.foreach(elem => x += elem.length) + } + def test2() = { + var y = 0 + xs10.foreachInlined(elem => y += elem.length) + } + test1() + test2() + + is0.foreach(i => assert(i == 1)) + is3.foreach(i => assert(i <= 3)) + } + + def mapTest() = { + val ys0 = xs0.map(_.reverse) + val ys1 = xs1.map(s => s + s) + assert(ys1.mkString == "aa") + val ys5 = xs5.map(s => s + s) + assert(ys5.mkString == "aa, bb, cc, dd, ee") + + val js0 = is0.map(i => i * i) + val js1 = is1.map(i => i + i) + assert(js1.mkString == "2") + val js5 = is5.map(s => s + s) + assert(js5.mkString == "2, 4, 6, 8, 10") + } + + def mapConserveTest() = { + val ys0 = xs0.mapConserve(_.reverse) + val ys1 = xs1.mapConserve(s => s + s) + assert(ys1.mkString == "aa") + val ys2 = xs2.mapConserve(identity) + assert(ys2 `eqLst` xs2) + val ys5 = xs5.map(s => s + s) + assert(ys5.mkString == "aa, bb, cc, dd, ee") + val ys4 = xs4.mapConserve(s => if (s == "c") "cc" else s) + assert(ys4.mkString == "a, b, cc, d") + + val js0 = is0.mapConserve(i => i * i) + val js1 = is1.mapConserve(s => s + s) + assert(js1.mkString == "2") + val js2 = is2.mapConserve(identity) + assert(js2 `eqLst` is2) + val js5 = is5.map(s => s + s) + assert(js5.mkString == "2, 4, 6, 8, 10") + val js4 = is4.mapConserve(s => if (s == 3) -3 else s) + assert(js4.mkString == "1, 2, -3, 4") + } + + def flatMapTest() = { + val ys0 = xs0.flatMap(s => Lst(s, s)) + assert(ys0.isEmpty) + val ys2 = xs2.flatMap(s => Lst(s, s)) + assert(ys2.mkString == "a, a, b, b") + val ys2a = xs2.flatMap(_ => Lst.Empty) + assert(ys2a.isEmpty) + val ys4 = xs4.flatMap(s => Lst.fill(s.head - 'a')(s)) + assert(ys4.mkString == "b, c, c, d, d, d") + val ys5 = xs5.flatMap(s => if s == "c" then Lst(s) else Lst()) + assert(ys5 == Lst("c")) + + val js0 = is0.flatMap(s => Lst(s, s)) + assert(js0.isEmpty) + val js2 = is2.flatMap(s => Lst(s, s)) + assert(js2.mkString == "1, 1, 2, 2") + val js2a = is2.flatMap(_ => Lst.Empty) + assert(js2a.isEmpty) + val js4 = is4.flatMap(s => Lst.fill(s - 1)(s)) + assert(js4.mkString == "2, 3, 3, 4, 4, 4", js4) + val js5 = is5.flatMap(s => if s == 3 then Lst(-3) else Lst()) + assert(js5 == Lst(-3)) + } + + def filterTest() = { + val ys0 = xs0.filter(_.head >= 'c') + assert(ys0.isEmpty) + val ys1 = xs1.filter(_.head >= 'c') + assert(ys1.isEmpty) + val ys1a = xs1.filterNot(_.head >= 'c') + assert(ys1a `eqLst` xs1) + val ys5 = xs5.filter(_.head % 2 != 0) + assert(ys5 === Lst("a", "c", "e"), ys5) + + val js0 = is0.filter(_ > 3) + assert(js0.isEmpty) + val js1 = is1.filter(_ > 3) + assert(js1.isEmpty) + val js1a = is1.filterNot(_ > 3) + assert(js1a `eqLst` is1) + val js5 = is5.filter(_ % 2 != 0) + assert(js5 === Lst(1, 3, 5), js5) + } + + def existsTest() = { + assert(!xs0.exists(_ => true)) + assert(xs1.exists(_ == "a")) + assert(xs5.exists(_ == "c")) + assert(!xs5.exists(_.head > 'e')) + + assert(!is0.exists(_ => true)) + assert(is1.exists(_ == 1)) + assert(is5.exists(_ == 3)) + assert(!is5.exists(_ > 5)) + } + + def forallTest() = { + assert(xs0.forall(_ => false)) + assert(xs1.forall(_ == "a")) + assert(xs5.forall(_.head <= 'e')) + assert(!xs5.forall(_ == "c")) + } + + def containsTest() = { + assert(!xs0.contains("")) + assert(xs1.contains("a")) + assert(xs10.contains("e")) + assert(!xs10.contains("f")) + + assert(!is0.contains(2)) + assert(is1.contains(1)) + assert(is10.contains(5)) + assert(!is10.contains(6)) + } + + def foldTest() = { + assert(xs0.foldLeft("x")(_ ++ _) == "x") + assert(xs1.foldLeft("x")(_ ++ _) == "xa") + assert(xs2.foldLeft("x")(_ ++ _) == "xab") + assert(xs3.foldLeft("x")(_ ++ _) == "xabc") + assert(("x" /: xs0)(_ ++ _) == "x") + assert(("x" /: xs1)(_ ++ _) == "xa") + assert(("x" /: xs2)(_ ++ _) == "xab") + assert(("x" /: xs3)(_ ++ _) == "xabc") + assert(xs1.reduceLeft(_ + _) == "a") + assert(xs3.reduceLeft(_ + _) == "abc") + + assert(is0.foldLeft(3)(_ + _) == 3) + assert(is1.foldLeft(3)(_ + _) == 4) + assert(is2.foldLeft(3)(_ + _) == 6) + assert(is3.foldLeft(3)(_ + _) == 9) + assert((3 /: is0)(_ + _) == 3) + assert((3 /: is1)(_ + _) == 4) + assert((3 /: is2)(_ + _) == 6) + assert((3 /: is3)(_ + _) == 9) + assert(is1.reduceLeft(_ + _) == 1) + assert(is3.reduceLeft(_ + _) == 6) + } + + def reverseTest() = { + assert(xs0.reverse === xs0) + assert(xs1.reverse === xs1) + assert(xs3.reverse.mkString == "c, b, a", xs3.reverse.mkString) + assert(xs4.reverse.reverse === xs4, xs4.reverse.reverse) + } + + def applyTest() = { + assert(xs5.head == "a") + assert(xs5.last == "e") + assert(xs5(3) == "d") + assert(xs1(0) == "a") + + assert(is5.head == 1) + assert(is5.last == 5) + assert(is5(3) == 4) + assert(is1(0) == 1) + } + + def sliceTest() = { + assert(xs5.slice(2, 4) === Lst("c", "d")) + assert(xs5.drop(4) === Lst("e")) + assert(xs5.take(4).mkString("") == "abcd") + assert(xs5.drop(-1) `eqLst` xs5) + assert(xs1.take(1) `eqLst` xs1) + assert(xs0.take(10).length == 0) + + assert(is5.slice(2, 4) === Lst(3, 4)) + assert(is5.drop(4) === Lst(5)) + assert(is5.take(4).mkString("") == "1234") + assert(is5.drop(-1) `eqLst` is5) + assert(is1.take(1) `eqLst` is1) + assert(is0.take(10).length == 0) + } + + def zipWithTest() = { + val ys4a = xs4.zipWith(xs5)(_ + _) + val ys4b = xs5.zipWith(xs4)(_ + _) + assert(ys4a.mkString("") == "aabbccdd", ys4a) + assert(ys4a === ys4b) + val ys1a = xs1.zipWith(xs1)(_ + _) + assert(ys1a === Lst("aa")) + val ys1b = xs1.zipWith(xs2)(_ + _) + assert(ys1b === Lst("aa")) + val ys1c = xs2.zipWith(xs1)(_ + _) + assert(ys1c === Lst("aa")) + val ys0a = xs1.zipWith(xs0)(_ + _) + val ys0b = xs0.zipWith(xs1)(_ + _) + assert((ys0a ++ ys0b).isEmpty) + val ys3i = xs3.zipWithIndex.map((x, y) => (x, y + 1)) + assert(ys3i === Lst(("a", 1), ("b", 2), ("c", 3)), ys3i) + + val js4a = is4.zipWith(is5)(_ + _) + val js4b = is5.zipWith(is4)(_ + _) + assert(js4a.mkString("") == "2468", js4a) + assert(js4a === js4b) + val js1a = is1.zipWith(is1)(_ + _) + assert(js1a === Lst(2)) + val js1b = is1.zipWith(is2)(_ + _) + assert(js1b === Lst(2)) + val js1c = is2.zipWith(is1)(_ + _) + assert(js1c === Lst(2)) + val js0a = is1.zipWith(is0)(_ + _) + val js0b = is0.zipWith(is1)(_ + _) + assert((js0a ++ js0b).isEmpty) + val js3i = is3.zipWithIndex.map((x, y) => (x, y + 1)) + assert(js3i === Lst((1, 1), (2, 2), (3, 3)), js3i) + assert(js3i.forall(_ == _)) + } + + def correspondsTest() = { + assert(xs4.corresponds(xs4)(_ == _)) + assert(!xs4.corresponds(xs5)(_ == _)) + assert(xs1.corresponds(xs1)(_ == _)) + assert(!xs1.corresponds(Lst("b"))(_ == _)) + assert(xs0.corresponds(xs0)(_ == _)) + assert(!xs0.corresponds(xs1)(_ == _)) + val zs1 = Lst(new Object, new Object) + val zs2 = Lst(zs1(0), zs1(1)) + val zs3 = Lst(new Object, new Object) + assert(zs1.eqElements(zs1)) + assert(zs1.eqElements(zs2)) + assert(!zs1.eqElements(zs3)) + + assert(is4.corresponds(is4)(_ == _)) + assert(!is4.corresponds(is5)(_ == _)) + assert(is1.corresponds(is1)(_ == _)) + assert(!is1.corresponds(Lst(-1))(_ == _)) + assert(is0.corresponds(is0)(_ == _)) + assert(!is0.corresponds(is1)(_ == _)) + } + + def bufferTest() = { + { val b = new Lst.Buffer[String] + b += "a" + assert(b.size == 1) + assert(b.toLst === Lst("a")) + b += "aa" + b ++= Lst.fill(20)("a") + assert(b.toLst.mkString("") == "a" * 23) + assert(b.size == 22) + } + + { val b = new Lst.Buffer[Int] + b += 1 + assert(b.size == 1) + assert(b.toLst === Lst(1)) + b += 11 + b ++= Lst.fill(20)(1) + assert(b.toLst.mkString("") == "1" * 23) + assert(b.size == 22) + } + } + + println("testing") + lengthTest() + concatTest() + foreachTest() + mapTest() + mapConserveTest() + flatMapTest() + filterTest() + existsTest() + forallTest() + containsTest() + foldTest() + reverseTest() + applyTest() + sliceTest() + zipWithTest() + correspondsTest() + bufferTest() +} \ No newline at end of file From 8784df9bb754d5ae38142678f573aa21cee2a47b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 2 May 2018 22:08:58 +0200 Subject: [PATCH 08/18] Count symbols in Stats --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 6e4df46b6530..70216841163a 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -16,6 +16,7 @@ import printing.Printer import Types._ import Annotations._ import util.Positions._ +import util.Stats import DenotTransformers._ import StdNames._ import NameOps._ @@ -416,6 +417,8 @@ object Symbols { //assert(id != 723) + Stats.record("symbol") + def coord: Coord = myCoord /** Set the coordinate of this class, this is only useful when the coordinate is * not known at symbol creation. This is the case for root symbols From 9b63140ad8a8f4fc37733dee98e74d1331c50cb7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 May 2018 19:29:36 +0200 Subject: [PATCH 09/18] Optimize typeParams Direct encoding of lazy val I also tried to mase typeParams tail recursive by addling a local `recur` method. But it turns out the previous typeParams is already fully optimized, no need to add a local function. --- compiler/src/dotty/tools/dotc/core/Types.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9e08d013b1b8..30fdaa6d3b31 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3007,8 +3007,13 @@ object Types { def newParamRef(n: Int) = new TypeParamRef(this, n) {} - lazy val typeParams: List[LambdaParam] = - paramNames.indices.toList.map(new LambdaParam(this, _)) + private[this] var myTypeParams: List[LambdaParam] = null + + def typeParams: List[LambdaParam] = { + if (myTypeParams == null) + myTypeParams = paramNames.indices.toList.map(new LambdaParam(this, _)) + myTypeParams + } def derivedLambdaAbstraction(paramNames: List[TypeName], paramInfos: List[TypeBounds], resType: Type)(implicit ctx: Context): Type = resType match { From 4c8ec5d04473b44a4a2be23bf224e6b24709c3b6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 4 May 2018 19:33:01 +0200 Subject: [PATCH 10/18] Add `Lst` to compiler codebase --- compiler/src/dotty/tools/dotc/util/Lst.scala | 501 +++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/util/Lst.scala diff --git a/compiler/src/dotty/tools/dotc/util/Lst.scala b/compiler/src/dotty/tools/dotc/util/Lst.scala new file mode 100644 index 000000000000..538c23f76a30 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/Lst.scala @@ -0,0 +1,501 @@ +package dotty.tools.dotc +package util + +import printing.{Printer, Texts} +import Texts.Text +import collection.mutable.{ListBuffer, StringBuilder} + + +/** A lightweight class for lists, optimized for short and medium lengths. + * A list is represented at runtime as + * + * If it is empty: the value `Lst.Empty` + * If it contains one element: the element itself + * If it contains more elements: an Array[Any] containing the elements + */ +// NOTE: `inline` is disabled here because to be useful you really need to inline +// the function argument of an inlined method like `foreach` or `map` as well. +// But scalac does not allow the `inline` modifier and `dotc` does not ipnterpret +// `@inline` annotation on function arguments as `inline`. Once we have a bootstrap +// we can drop all comment brackets around `inline`. +class Lst[+T](val elems: Any) extends AnyVal { + import Lst._ + + def length: Int = elems match { + case null => 0 + case elems: Arr => elems.length + case elem => 1 + } + + def isEmpty = elems == null + def nonEmpty = elems != null + + /*inline*/ def foreach(/*inline*/ op: T => Unit): Unit = { + def sharedOp(x: T) = op(x) + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { sharedOp(elem(i)); i += 1 } + case elem: T @ unchecked => sharedOp(elem) + } + } + + /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. + * Should be used only of `op` is small + */ + /*inline*/ def foreachInlined(/*inline*/ op: T => Unit): Unit = elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { op(elem(i)); i += 1 } + case elem: T @unchecked => op(elem) + } + + def iterator(): Iterator[T] = elems match { + case null => Iterator.empty + case elems: Arr => elems.iterator.asInstanceOf[Iterator[T]] + case elem: T @unchecked => Iterator.single(elem) + } + + def copyToArray[U >: T](target: Array[U], from: Int) = elems match { + case null => + case elems: Arr => System.arraycopy(elems, 0, target, from, elems.length) + case elem: T @ unchecked => target(from) = elem + } + + /** `f` is pulled out, not duplicated */ + /*inline*/ def map[U](/*inline*/ f: T => U): Lst[U] = { + def op(x: T) = f(x) + elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } + new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](op(elem)) + } + } + + def mapConserve[U](f: T => U): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var newElems: Arr = null + var i = 0 + while (i < elems.length) { + val x = elem(i) + val y = f(x) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(elems.length) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](f(elem)) + } + + def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElemss: Arr = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val ys = f(elem(i)) + len += ys.length + newElemss(i) = ys.elems + i += 1 + } + if (len == 0) Empty + else if (len == 1) { + i = 0 + while (newElemss(i) == null) i += 1 + new Lst[U](newElemss(i)) + } + else { + val newElems = new Arr(len) + i = 0 + var j = 0 + while (i < newElemss.length) { + val ys = new Lst[U](newElemss(i)) + ys.copyToArray(newElems, j) + j += ys.length + i += 1 + } + new Lst[U](newElems) + } + case elem: T @ unchecked => new Lst[U](f(elem).elems) + } + + def filter(p: T => Boolean): Lst[T] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val scratch = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val x = elem(i) + if (p(x)) { scratch(len) = x; len += 1 } + i += 1 + } + if (len == elems.length) this + else _fromArray(scratch, 0, len) + case elem: T @unchecked => + if (p(elem)) this else Empty + } + def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) + + /*inline*/ def exists(/*inline*/ p: T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && !op(elem(i))) i += 1 + i < elems.length + case elem: T @unchecked => + op(elem) + } + } + + /*inline*/ def forall(/*inline*/ p: T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => true + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && op(elem(i))) i += 1 + i == elems.length + case elem: T @unchecked => + op(elem) + } + } + + /*inline*/ def contains[U >: T](x: U): Boolean = elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && elem(i) != x) i += 1 + i < elems.length + case elem: T @unchecked => + elem == x + } + + /*inline*/ def foldLeft[U](z: U)(/*inline*/ f: (U, T) => U) = { + def op(x: U, y: T) = f(x, y) + elems match { + case null => z + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + var acc = z + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + op(z, elem) + } + } + + /*inline*/ def /: [U](z: U)(/*inline*/ op: (U, T) => U) = foldLeft(z)(op) + + def reduceLeft[U >: T](op: (U, U) => U) = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 1 + var acc: U = elem(0) + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + elem + } + + def reverse: Lst[T] = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { + newElems(elems.length - 1 - i) = elem(i) + i += 1 + } + new Lst[T](newElems) + case _ => this + } + + def apply(n: Int): T = elems match { + case null => throw new IndexOutOfBoundsException(n.toString) + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + elem(n) + case elem: T @unchecked => + if (n == 0) elem else throw new IndexOutOfBoundsException(n.toString) + } + + def head: T = apply(0) + def last: T = apply(length - 1) + + def headOr[U >: T](alt: => U): U = if (isEmpty) alt else head + + def slice(start: Int, end: Int): Lst[T] = + if (start < 0) slice(0, end) + else elems match { + case null => this + case elems: Arr => _fromArray(elems, start, end `min` elems.length) + case elem: T @ unchecked => if (end == 0) Empty else this + } + + def drop(n: Int): Lst[T] = slice(n, length) + def tail = drop(1) + def take(n: Int): Lst[T] = slice(0, n) + + def ++ [U >: T](that: Lst[U]): Lst[U] = + if (elems == null) that + else if (that.elems == null) this + else { + val len1 = this.length + val len2 = that.length + val newElems = new Arr(len1 + len2) + this.copyToArray(newElems, 0) + that.copyToArray(newElems, len1) + new Lst[U](newElems) + } + + def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = + this.elems match { + case null => Empty + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length min elems2.length + if (len == 0) Empty + else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) + else { + var newElems: Arr = null + var i = 0 + while (i < len) { + val x = elem1(i) + val y = op(x, elem2(i)) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(len) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + new Lst[V](newElems) + } + case elem2: U @unchecked => + new Lst[V](op(elem1(0), elem2)) + } + case elem1: T @unchecked => + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + new Lst[V](op(elem1, elem2(0))) + case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) + } + } + + def zip[U](that: Lst[U]): Lst[(T, U)] = zipWith(that)((_, _)) + + def zipWithIndex: Lst[(T, Int)] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } + new Lst[(T, Int)](newElems) + case elem: T @unchecked => + new Lst[(T, Int)]((elem, 0)) + } + + def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = + (this `eqLst` that) || { + this.elems match { + case null => + that.elems == null + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && p(elem1(i), elem2(i))) i += 1 + i == len + } + case _ => false + } + case elem1: T @unchecked => + that.elems match { + case null => false + case elems2: Arr => false + case elem2: U @unchecked => p(elem1, elem2) + } + } + } + + def === [U](that: Lst[U]) = + (this `eqLst` that) || { + elems match { + case elems1: Arr => + that.elems match { + case elems2: Arr => + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && elems1(i).equals(elems2(i))) i += 1 + i == len + } + case _ => false + } + case elem => elem == that.elems + } + } + + def eqLst[U](that: Lst[U]) = eq(elems, that.elems) + + def eqElements[U](that: Lst[U]): Boolean = corresponds(that)(eqFn) + + def mkString: String = mkString(", ") + + def mkString(sep: String): String = mkString("", sep, "") + def mkString(left: String, sep: String, right: String): String = { + val b = new StringBuilder(left) + var first = true + foreachInlined { elem => + if (first) first = false else b ++= sep + b ++= elem.toString + } + b ++= right + b.toString + } + + override def toString = mkString("Lst(", ", ", ")") +} + +object Lst { + + private type Arr = Array[Any] + + private def eq(x: Any, y: Any) = x.asInstanceOf[AnyRef] `eq` y.asInstanceOf[AnyRef] + private val eqFn = (x: Any, y: Any) => eq(x, y) + + val Empty = new Lst[Nothing](null) + + def apply[T](): Lst[T] = Empty + + def apply[T](x0: T): Lst[T] = new Lst[T](x0) + + def apply[T](x0: T, x1: T): Lst[T] = { + val elems = new Arr(2) + elems(0) = x0 + elems(1) = x1 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T): Lst[T] = { + val elems = new Arr(3) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T): Lst[T] = { + val elems = new Arr(4) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T, x4: T, xs: T*): Lst[T] = { + val elems = new Arr(5 + xs.length) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + elems(4) = x4 + xs.copyToArray(elems, 5) + new Lst[T](elems) + } + + def fill[T](n: Int)(elem: => T) = { + val elems = new Arr(n) + var i = 0 + while (i < n) { elems(i) = elem; i += 1} + new Lst[T](elems) + } + + class Buffer[T] { + private var len = 0 + private var elem: T = _ + private var elems: Arr = _ + + def size = len + + /** pre: len > 0, n > 1 */ + private def ensureSize(n: Int) = + if (len == 1) { + elems = new Arr(n `max` 16) + elems(0) = elem + } + else if (len < n) { + val newLen = n `max` len << 2 + val newElems = new Arr(newLen) + System.arraycopy(elems, 0, newElems, 0, len) + elems = newElems + } + + def += (x: T): this.type = { + if (len == 0) elem = x + else { + ensureSize(len + 1) + elems(len) = x + } + len += 1 + this + } + + def ++= (xs: Lst[T]): this.type = { + xs.elems match { + case null => this + case elems2: Arr => + if (len == 0) elems = elems2 + else { + ensureSize(len + elems2.length) + System.arraycopy(elems2, 0, elems, len, elems2.length) + } + len += elems2.length + case elem: T @unchecked => this += elem + } + this + } + + def toLst: Lst[T] = + if (len == 0) Empty + else if (len == 1) new Lst[T](elem) + else _fromArray(elems, 0, len) + + def clear() = + len = 0 + } + + private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { + val len = end - start + if (len <= 0) Empty + else if (len == 1) new Lst[T](elems(start)) + else if (start == 0 && end == elems.length) new Lst[T](elems) + else { + val newElems = new Arr(len) + System.arraycopy(elems, start, newElems, 0, len) + new Lst[T](newElems) + } + } + + def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = + _fromArray(elems.asInstanceOf[Arr], start, end) +} From 97debea600cc4567fdf5fd2f997f69dd5d4b703c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 May 2018 15:35:12 +0200 Subject: [PATCH 11/18] Allow Lst of Array --- tests/run/lst/Lst.scala | 65 +++++++++++++++++++++++-------------- tests/run/lst/LstTest.check | 1 + 2 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 tests/run/lst/LstTest.check diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index f501bff72381..57af4f691286 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -5,13 +5,13 @@ import printing.{Printer, Texts} import Texts.Text import collection.mutable.{ListBuffer, StringBuilder} - /** A lightweight class for lists, optimized for short and medium lengths. * A list is represented at runtime as * - * If it is empty: the value `Lst.Empty` - * If it contains one element: the element itself - * If it contains more elements: an Array[Any] containing the elements + * If it is empty: the value `Lst.Empty` + * If it contains one element, + * and the element is not an array: the element itself + * Otherwise: an Array[Any] containing the elements */ class Lst[+T](val elems: Any) extends AnyVal { import Lst._ @@ -68,8 +68,8 @@ class Lst[+T](val elems: Any) extends AnyVal { val newElems = new Arr(elems.length) var i = 0 while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } - new Lst[U](newElems) - case elem: T @ unchecked => new Lst[U](op(elem)) + multi[U](newElems) + case elem: T @ unchecked => single[U](op(elem)) } } @@ -89,8 +89,8 @@ class Lst[+T](val elems: Any) extends AnyVal { } i += 1 } - if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) - case elem: T @ unchecked => new Lst[U](f(elem)) + if (newElems == null) this.asInstanceOf[Lst[U]] else multi[U](newElems) + case elem: T @ unchecked => single[U](f(elem)) } def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { @@ -121,9 +121,9 @@ class Lst[+T](val elems: Any) extends AnyVal { j += ys.length i += 1 } - new Lst[U](newElems) + multi[U](newElems) } - case elem: T @ unchecked => new Lst[U](f(elem).elems) + case elem: T @ unchecked => f(elem) } def filter(p: T => Boolean): Lst[T] = elems match { @@ -214,7 +214,7 @@ class Lst[+T](val elems: Any) extends AnyVal { newElems(elems.length - 1 - i) = elem(i) i += 1 } - new Lst[T](newElems) + multi[T](newElems) case _ => this } @@ -252,7 +252,7 @@ class Lst[+T](val elems: Any) extends AnyVal { val newElems = new Arr(len1 + len2) this.copyToArray(newElems, 0) that.copyToArray(newElems, len1) - new Lst[U](newElems) + multi[U](newElems) } def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = @@ -264,7 +264,7 @@ class Lst[+T](val elems: Any) extends AnyVal { case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] val len = elems1.length min elems2.length if (len == 0) Empty - else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) + else if (len == 1) single[V](op(elem1(0), elem2(0))) else { var newElems: Arr = null var i = 0 @@ -279,17 +279,17 @@ class Lst[+T](val elems: Any) extends AnyVal { } i += 1 } - new Lst[V](newElems) + multi[V](newElems) } case elem2: U @unchecked => - new Lst[V](op(elem1(0), elem2)) + single[V](op(elem1(0), elem2)) } case elem1: T @unchecked => that.elems match { case null => Empty case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] - new Lst[V](op(elem1, elem2(0))) - case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) + single[V](op(elem1, elem2(0))) + case elem2: U @unchecked => single[V](op(elem1, elem2)) } } @@ -301,9 +301,9 @@ class Lst[+T](val elems: Any) extends AnyVal { val newElems = new Arr(elems.length) var i = 0 while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } - new Lst[(T, Int)](newElems) + multi[(T, Int)](newElems) case elem: T @unchecked => - new Lst[(T, Int)]((elem, 0)) + single[(T, Int)]((elem, 0)) } def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = @@ -381,7 +381,7 @@ object Lst { def apply[T](): Lst[T] = Empty - def apply[T](x0: T): Lst[T] = new Lst[T](x0) + def apply[T](x0: T): Lst[T] = single[T](x0) def apply[T](x0: T, x1: T): Lst[T] = { val elems = new Arr(2) @@ -427,12 +427,12 @@ object Lst { class Buffer[T] { private var len = 0 - private var elem: T = _ + private var elem: Any = _ private var elems: Arr = _ def size = len - /** pre: len > 0, n > 1 */ + /** pre: len > 0, n >= 1 */ private def ensureSize(n: Int) = if (len == 1) { elems = new Arr(n `max` 16) @@ -459,7 +459,9 @@ object Lst { xs.elems match { case null => this case elems2: Arr => - if (len == 0) elems = elems2 + if (len == 0 && elems2.length != 1) + // if elems2.length == 1, elems2 is a wrapped single element list, which has to be unpacked + elems = elems2 else { ensureSize(len + elems2.length) System.arraycopy(elems2, 0, elems, len, elems2.length) @@ -472,17 +474,30 @@ object Lst { def toLst: Lst[T] = if (len == 0) Empty - else if (len == 1) new Lst[T](elem) + else if (len == 1) single(elem) else _fromArray(elems, 0, len) def clear() = len = 0 } + private def single[T](elem: Any): Lst[T] = elem match { + case elem: Arr => + val wrapped = new Arr(1) + wrapped(0) = elem + new Lst[T](wrapped) + case _ => + new Lst[T](elem) + } + + private def multi[T](elems: Array[Any]) = + if (elems.length == 1) new Lst[T](elems(0)) + else new Lst[T](elems) + private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { val len = end - start if (len <= 0) Empty - else if (len == 1) new Lst[T](elems(start)) + else if (len == 1) single[T](elems(start)) else if (start == 0 && end == elems.length) new Lst[T](elems) else { val newElems = new Arr(len) diff --git a/tests/run/lst/LstTest.check b/tests/run/lst/LstTest.check new file mode 100644 index 000000000000..038d718da6a1 --- /dev/null +++ b/tests/run/lst/LstTest.check @@ -0,0 +1 @@ +testing From 8f9043c16de3db33c4b078e9d40e1613a99a7d78 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 May 2018 11:38:36 +0200 Subject: [PATCH 12/18] Allow Lst of Lsts --- tests/run/lst/Lst.scala | 4 +- tests/run/lst/LstTest.check | 5 +- tests/run/lst/LstTest.scala | 101 +++++++++++++++++++++++++++++++++++- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 57af4f691286..f488bca290fa 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -482,7 +482,7 @@ object Lst { } private def single[T](elem: Any): Lst[T] = elem match { - case elem: Arr => + case null | _: Arr => val wrapped = new Arr(1) wrapped(0) = elem new Lst[T](wrapped) @@ -491,7 +491,7 @@ object Lst { } private def multi[T](elems: Array[Any]) = - if (elems.length == 1) new Lst[T](elems(0)) + if (elems.length == 1) single(elems(0)) else new Lst[T](elems) private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { diff --git a/tests/run/lst/LstTest.check b/tests/run/lst/LstTest.check index 038d718da6a1..ac3bf9813758 100644 --- a/tests/run/lst/LstTest.check +++ b/tests/run/lst/LstTest.check @@ -1 +1,4 @@ -testing +Lst(Lst()) +Lst(Lst(a)) +Lst(Lst(a), Lst(b), Lst(c), Lst(d), Lst(e)) +Lst(Lst(a), Lst(b), Lst(c), Lst(d), Lst(e), Lst(a), Lst(b), Lst(c), Lst(d), Lst(e)) diff --git a/tests/run/lst/LstTest.scala b/tests/run/lst/LstTest.scala index 87f2541fb62a..9a0b80a356bf 100644 --- a/tests/run/lst/LstTest.scala +++ b/tests/run/lst/LstTest.scala @@ -17,6 +17,19 @@ object Test extends App { val is5 = Lst(1, 2, 3, 4, 5) val is10 = is5 ++ is5 + val xss0: Lst[Lst[String]] = Lst(Lst()) + val xss1 = Lst(Lst("a")) + val xss2 = Lst(Lst("a"), Lst("b")) + val xss3 = Lst(Lst("a"), Lst("b"), Lst("c")) + val xss4 = Lst(Lst("a"), Lst("b"), Lst("c"), Lst("d")) + val xss5 = Lst(Lst("a"), Lst("b"), Lst("c"), Lst("d"), Lst("e")) + val xss10 = xss5 ++ xss5 + + println(xss0) + println(xss1) + println(xss5) + println(xss10) + def lengthTest() = { assert(xs0.length == 0) assert(xs1.length == 1) @@ -27,6 +40,11 @@ object Test extends App { assert(is1.length == 1) assert(is2.length == 2) assert(is10.length == 10) + + assert(xss0.length == 1) + assert(xss1.length == 1) + assert(xss2.length == 2) + assert(xss10.length == 10) } def concatTest() = { @@ -39,6 +57,11 @@ object Test extends App { assert(is0 ++ is1 == is1) assert(is3 ++ is0 == is3) assert(is0 ++ is4 == is4) + + assert(xss1 ++ xs0 == xss1) + assert(xs0 ++ xss1 == xss1) + assert(xss3 ++ xs0 == xss3) + assert(xs0 ++ xss4 == xss4) } def foreachTest() = { @@ -57,6 +80,9 @@ object Test extends App { is0.foreach(i => assert(i == 1)) is3.foreach(i => assert(i <= 3)) + + xss0.foreach(s => assert(s.length == 0)) + xss3.foreach(s => assert(s.length == 1)) } def mapTest() = { @@ -71,6 +97,13 @@ object Test extends App { assert(js1.mkString == "2") val js5 = is5.map(s => s + s) assert(js5.mkString == "2, 4, 6, 8, 10") + + val yss0 = xss0.map(_.reverse) + assert(yss0.mkString == "Lst()", yss0.mkString) + val yss1 = xss1.map(s => s ++ s) + assert(yss1.mkString == "Lst(a, a)", yss1.mkString) + val yss5 = xss5.map(s => s ++ s) + assert(yss5.mkString == "Lst(a, a), Lst(b, b), Lst(c, c), Lst(d, d), Lst(e, e)") } def mapConserveTest() = { @@ -93,6 +126,13 @@ object Test extends App { assert(js5.mkString == "2, 4, 6, 8, 10") val js4 = is4.mapConserve(s => if (s == 3) -3 else s) assert(js4.mkString == "1, 2, -3, 4") + + val yss0 = xss0.mapConserve(_.reverse) + assert(yss0.mkString == "Lst()", yss0.mkString) + val yss1 = xss1.mapConserve(s => s ++ s) + assert(yss1.mkString == "Lst(a, a)", yss1.mkString) + val yss5 = xss5.mapConserve(s => s ++ s) + assert(yss5.mkString == "Lst(a, a), Lst(b, b), Lst(c, c), Lst(d, d), Lst(e, e)") } def flatMapTest() = { @@ -117,6 +157,17 @@ object Test extends App { assert(js4.mkString == "2, 3, 3, 4, 4, 4", js4) val js5 = is5.flatMap(s => if s == 3 then Lst(-3) else Lst()) assert(js5 == Lst(-3)) + + val yss0 = xss0.flatMap(s => Lst(s, s)) + assert(yss0.length == 2 && yss0.forall(_.isEmpty)) + val yss2 = xss2.flatMap(s => Lst(s, s)) + assert(yss2.mkString == "Lst(a), Lst(a), Lst(b), Lst(b)") + val yss2a = xss2.flatMap(_ => Lst.Empty) + assert(yss2a.isEmpty) + val yss4 = xss4.flatMap(s => Lst.fill(s.head.head - 'a')(s)) + assert(yss4.mkString == "Lst(b), Lst(c), Lst(c), Lst(d), Lst(d), Lst(d)", yss4.mkString) + val yss5 = xss5.flatMap(s => if s.head == "c" then Lst(s) else Lst()) + assert(yss5 == Lst(Lst("c"))) } def filterTest() = { @@ -137,6 +188,17 @@ object Test extends App { assert(js1a `eqLst` is1) val js5 = is5.filter(_ % 2 != 0) assert(js5 === Lst(1, 3, 5), js5) + + val yss0 = xss0.filter(_.nonEmpty) + assert(yss0.isEmpty) + val yss1 = xss1.filter(_.head.head >= 'c') + assert(yss1.isEmpty) + val yss1a = xss1.filterNot(_.head.head >= 'c') + assert(yss1a `eqLst` xss1) + val yss5 = xss5.filter(_.head.head % 2 != 0) + assert(yss5 === Lst(Lst("a"), Lst("c"), Lst("e")), yss5) + val yss5a = xss5.map(_.filter(_.head % 2 != 0)) + assert(yss5a === Lst(Lst("a"), Lst(), Lst("c"), Lst(), Lst("e")), yss5) } def existsTest() = { @@ -149,6 +211,13 @@ object Test extends App { assert(is1.exists(_ == 1)) assert(is5.exists(_ == 3)) assert(!is5.exists(_ > 5)) + + assert(!xss0.exists(_.exists(_ => true))) + assert(xss1.exists(_.head == "a")) + assert(xss1.exists(_.exists(_ == "a"))) + assert(xss5.exists(_.head == "c")) + assert(xss5.exists(_.exists(_ == "c"))) + assert(!xss5.exists(_.head.head > 'e')) } def forallTest() = { @@ -168,6 +237,11 @@ object Test extends App { assert(is1.contains(1)) assert(is10.contains(5)) assert(!is10.contains(6)) + + assert(!xss0.contains(List(""))) + assert(xss1.contains(Lst("a"))) + assert(xss10.contains(Lst("e")), xss10) + assert(!xss10.contains(Lst("f"))) } def foldTest() = { @@ -192,6 +266,17 @@ object Test extends App { assert((3 /: is3)(_ + _) == 9) assert(is1.reduceLeft(_ + _) == 1) assert(is3.reduceLeft(_ + _) == 6) + + assert(xss0.foldLeft(Lst("x"))(_ ++ _) === Lst("x")) + assert(xss1.foldLeft(Lst("x"))(_ ++ _) === Lst("x", "a")) + assert(xss2.foldLeft(Lst(): Lst[String])(_ ++ _) === Lst("a", "b")) + assert(xss3.foldLeft(Lst.Empty: Lst[String])(_ ++ _) === Lst("a", "b", "c")) + assert((Lst("x") /: xss0)(_ ++ _) === Lst("x")) + assert((Lst("x") /: xss1)(_ ++ _) === Lst("x", "a")) + assert((Lst("x") /: xss2)(_ ++ _) === Lst("x", "a", "b")) + assert((Lst("x") /: xss3)(_ ++ _) === Lst("x", "a", "b", "c")) + assert(xss1.reduceLeft(_ ++ _) === Lst("a")) + assert(xss3.reduceLeft(_ ++ _) === Lst("a", "b", "c")) } def reverseTest() = { @@ -211,6 +296,11 @@ object Test extends App { assert(is5.last == 5) assert(is5(3) == 4) assert(is1(0) == 1) + + assert(xss5.head.head == "a") + assert(xss5.last.head == "e") + assert(xss5(3).head == "d") + assert(xss1(0).head == "a") } def sliceTest() = { @@ -306,9 +396,18 @@ object Test extends App { assert(b.toLst.mkString("") == "1" * 23) assert(b.size == 22) } + + { val b = new Lst.Buffer[Lst[String]] + b += Lst("a") + assert(b.size == 1) + assert(b.toLst === Lst(Lst("a"))) + b += Lst("aa") + b ++= Lst.fill(20)(Lst("a")) + assert(b.toLst.map(_.mkString("")).mkString("") == "a" * 23) + assert(b.size == 22) + } } - println("testing") lengthTest() concatTest() foreachTest() From a94ed0eeee62eaa77225514a9b4acfeb6c8eadbc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 May 2018 16:10:02 +0200 Subject: [PATCH 13/18] Let classSymbols return a Lst --- .../src/dotty/tools/dotc/core/Types.scala | 14 ++-- .../dotty/tools/dotc/typer/Implicits.scala | 19 ++--- compiler/src/dotty/tools/dotc/util/Lst.scala | 80 ++++++++++++------- tests/run/lst/Lst.scala | 26 ++++-- 4 files changed, 85 insertions(+), 54 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 30fdaa6d3b31..ec569c22784f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -18,7 +18,7 @@ import Denotations._ import Periods._ import util.Positions.{Position, NoPosition} import util.Stats._ -import util.{DotClass, SimpleIdentitySet} +import util.{DotClass, SimpleIdentitySet, Lst} import reporting.diagnostic.Message import reporting.diagnostic.messages.CyclicReferenceInvolving import ast.tpd._ @@ -394,20 +394,20 @@ object Types { /** The least (wrt <:<) set of class symbols of which this type is a subtype */ - final def classSymbols(implicit ctx: Context): List[ClassSymbol] = this match { + final def classSymbols(implicit ctx: Context): Lst[ClassSymbol] = this match { case tp: ClassInfo => - tp.cls :: Nil + Lst(tp.cls) case tp: TypeRef => val sym = tp.symbol - if (sym.isClass) sym.asClass :: Nil else tp.superType.classSymbols: @tailrec + if (sym.isClass) Lst(sym.asClass) else tp.superType.classSymbols: @tailrec case tp: TypeProxy => tp.underlying.classSymbols: @tailrec case AndType(l, r) => - l.classSymbols union r.classSymbols + l.classSymbols `union` r.classSymbols case OrType(l, r) => - l.classSymbols intersect r.classSymbols // TODO does not conform to spec + l.classSymbols `intersect` r.classSymbols // TODO does not conform to spec case _ => - Nil + Lst() } /** The term symbol associated with the type */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5990b102dac0..bb7f707f1c23 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -31,7 +31,7 @@ import reporting.diagnostic.{Message, MessageContainer} import Inferencing.fullyDefinedType import Trees._ import Hashable._ -import util.Property +import util.{Property, Lst} import config.Config import config.Printers.{implicits, implicitsDetailed, typr} import collection.mutable @@ -1174,18 +1174,19 @@ class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { new SearchHistory(searchDepth + 1, seen) else { val size = typeSize(proto) - def updateMap(csyms: List[ClassSymbol], seen: Map[ClassSymbol, Int]): SearchHistory = csyms match { - case csym :: csyms1 => - seen get csym match { + val csyms = proto.classSymbols + def updateMap(idx: Int, seen: Map[ClassSymbol, Int]): SearchHistory = + if (idx < csyms.length) { + val csym = csyms(idx) + seen.get(csym) match { // proto complexity is >= than the last time it was seen → diverge case Some(prevSize) if size >= prevSize => this - case _ => updateMap(csyms1, seen.updated(csym, size)) + case _ => updateMap(idx + 1, seen.updated(csym, size)) } - case _ => + } + else new SearchHistory(searchDepth + 1, seen) - } - if (proto.classSymbols.isEmpty) this - else updateMap(proto.classSymbols, seen) + if (csyms.isEmpty) this else updateMap(0, seen) } } diff --git a/compiler/src/dotty/tools/dotc/util/Lst.scala b/compiler/src/dotty/tools/dotc/util/Lst.scala index 538c23f76a30..c1c5364b862f 100644 --- a/compiler/src/dotty/tools/dotc/util/Lst.scala +++ b/compiler/src/dotty/tools/dotc/util/Lst.scala @@ -4,20 +4,16 @@ package util import printing.{Printer, Texts} import Texts.Text import collection.mutable.{ListBuffer, StringBuilder} - +import reflect.ClassTag /** A lightweight class for lists, optimized for short and medium lengths. * A list is represented at runtime as * - * If it is empty: the value `Lst.Empty` - * If it contains one element: the element itself - * If it contains more elements: an Array[Any] containing the elements + * If it is empty: the value `Lst.Empty` + * If it contains one element, + * and the element is not an array: the element itself + * Otherwise: an Array[Any] containing the elements */ -// NOTE: `inline` is disabled here because to be useful you really need to inline -// the function argument of an inlined method like `foreach` or `map` as well. -// But scalac does not allow the `inline` modifier and `dotc` does not ipnterpret -// `@inline` annotation on function arguments as `inline`. Once we have a bootstrap -// we can drop all comment brackets around `inline`. class Lst[+T](val elems: Any) extends AnyVal { import Lst._ @@ -64,6 +60,12 @@ class Lst[+T](val elems: Any) extends AnyVal { case elem: T @ unchecked => target(from) = elem } + def toArray[U >: T : ClassTag]: Array[U] = { + val result = new Array[U](length) + copyToArray(result, 0) + result + } + /** `f` is pulled out, not duplicated */ /*inline*/ def map[U](/*inline*/ f: T => U): Lst[U] = { def op(x: T) = f(x) @@ -73,8 +75,8 @@ class Lst[+T](val elems: Any) extends AnyVal { val newElems = new Arr(elems.length) var i = 0 while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } - new Lst[U](newElems) - case elem: T @ unchecked => new Lst[U](op(elem)) + multi[U](newElems) + case elem: T @ unchecked => single[U](op(elem)) } } @@ -94,8 +96,8 @@ class Lst[+T](val elems: Any) extends AnyVal { } i += 1 } - if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) - case elem: T @ unchecked => new Lst[U](f(elem)) + if (newElems == null) this.asInstanceOf[Lst[U]] else multi[U](newElems) + case elem: T @ unchecked => single[U](f(elem)) } def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { @@ -126,9 +128,9 @@ class Lst[+T](val elems: Any) extends AnyVal { j += ys.length i += 1 } - new Lst[U](newElems) + multi[U](newElems) } - case elem: T @ unchecked => new Lst[U](f(elem).elems) + case elem: T @ unchecked => f(elem) } def filter(p: T => Boolean): Lst[T] = elems match { @@ -219,7 +221,7 @@ class Lst[+T](val elems: Any) extends AnyVal { newElems(elems.length - 1 - i) = elem(i) i += 1 } - new Lst[T](newElems) + multi[T](newElems) case _ => this } @@ -257,9 +259,12 @@ class Lst[+T](val elems: Any) extends AnyVal { val newElems = new Arr(len1 + len2) this.copyToArray(newElems, 0) that.copyToArray(newElems, len1) - new Lst[U](newElems) + multi[U](newElems) } + def union [U >: T](that: Lst[U]): Lst[U] = this ++ that.filterNot(this.contains) + def intersect [U >: T](that: Lst[U]): Lst[U] = this.filter(that.contains) + def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = this.elems match { case null => Empty @@ -269,7 +274,7 @@ class Lst[+T](val elems: Any) extends AnyVal { case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] val len = elems1.length min elems2.length if (len == 0) Empty - else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) + else if (len == 1) single[V](op(elem1(0), elem2(0))) else { var newElems: Arr = null var i = 0 @@ -284,17 +289,17 @@ class Lst[+T](val elems: Any) extends AnyVal { } i += 1 } - new Lst[V](newElems) + multi[V](newElems) } case elem2: U @unchecked => - new Lst[V](op(elem1(0), elem2)) + single[V](op(elem1(0), elem2)) } case elem1: T @unchecked => that.elems match { case null => Empty case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] - new Lst[V](op(elem1, elem2(0))) - case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) + single[V](op(elem1, elem2(0))) + case elem2: U @unchecked => single[V](op(elem1, elem2)) } } @@ -306,9 +311,9 @@ class Lst[+T](val elems: Any) extends AnyVal { val newElems = new Arr(elems.length) var i = 0 while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } - new Lst[(T, Int)](newElems) + multi[(T, Int)](newElems) case elem: T @unchecked => - new Lst[(T, Int)]((elem, 0)) + single[(T, Int)]((elem, 0)) } def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = @@ -386,7 +391,7 @@ object Lst { def apply[T](): Lst[T] = Empty - def apply[T](x0: T): Lst[T] = new Lst[T](x0) + def apply[T](x0: T): Lst[T] = single[T](x0) def apply[T](x0: T, x1: T): Lst[T] = { val elems = new Arr(2) @@ -432,12 +437,12 @@ object Lst { class Buffer[T] { private var len = 0 - private var elem: T = _ + private var elem: Any = _ private var elems: Arr = _ def size = len - /** pre: len > 0, n > 1 */ + /** pre: len > 0, n >= 1 */ private def ensureSize(n: Int) = if (len == 1) { elems = new Arr(n `max` 16) @@ -464,7 +469,9 @@ object Lst { xs.elems match { case null => this case elems2: Arr => - if (len == 0) elems = elems2 + if (len == 0 && elems2.length != 1) + // if elems2.length == 1, elems2 is a wrapped single element list, which has to be unpacked + elems = elems2 else { ensureSize(len + elems2.length) System.arraycopy(elems2, 0, elems, len, elems2.length) @@ -477,17 +484,30 @@ object Lst { def toLst: Lst[T] = if (len == 0) Empty - else if (len == 1) new Lst[T](elem) + else if (len == 1) single(elem) else _fromArray(elems, 0, len) def clear() = len = 0 } + private def single[T](elem: Any): Lst[T] = elem match { + case null | _: Arr => + val wrapped = new Arr(1) + wrapped(0) = elem + new Lst[T](wrapped) + case _ => + new Lst[T](elem) + } + + private def multi[T](elems: Array[Any]) = + if (elems.length == 1) single(elems(0)) + else new Lst[T](elems) + private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { val len = end - start if (len <= 0) Empty - else if (len == 1) new Lst[T](elems(start)) + else if (len == 1) single[T](elems(start)) else if (start == 0 && end == elems.length) new Lst[T](elems) else { val newElems = new Arr(len) diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index f488bca290fa..c1c5364b862f 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -4,6 +4,7 @@ package util import printing.{Printer, Texts} import Texts.Text import collection.mutable.{ListBuffer, StringBuilder} +import reflect.ClassTag /** A lightweight class for lists, optimized for short and medium lengths. * A list is represented at runtime as @@ -25,7 +26,7 @@ class Lst[+T](val elems: Any) extends AnyVal { def isEmpty = elems == null def nonEmpty = elems != null - inline def foreach(inline op: T => Unit): Unit = { + /*inline*/ def foreach(/*inline*/ op: T => Unit): Unit = { def sharedOp(x: T) = op(x) elems match { case null => @@ -39,7 +40,7 @@ class Lst[+T](val elems: Any) extends AnyVal { /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ - inline def foreachInlined(inline op: T => Unit): Unit = elems match { + /*inline*/ def foreachInlined(/*inline*/ op: T => Unit): Unit = elems match { case null => case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 0 @@ -59,8 +60,14 @@ class Lst[+T](val elems: Any) extends AnyVal { case elem: T @ unchecked => target(from) = elem } + def toArray[U >: T : ClassTag]: Array[U] = { + val result = new Array[U](length) + copyToArray(result, 0) + result + } + /** `f` is pulled out, not duplicated */ - inline def map[U](inline f: T => U): Lst[U] = { + /*inline*/ def map[U](/*inline*/ f: T => U): Lst[U] = { def op(x: T) = f(x) elems match { case null => Empty @@ -144,7 +151,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) - inline def exists(inline p: T => Boolean): Boolean = { + /*inline*/ def exists(/*inline*/ p: T => Boolean): Boolean = { def op(x: T) = p(x) elems match { case null => false @@ -157,7 +164,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } } - inline def forall(inline p: T => Boolean): Boolean = { + /*inline*/ def forall(/*inline*/ p: T => Boolean): Boolean = { def op(x: T) = p(x) elems match { case null => true @@ -170,7 +177,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } } - inline def contains[U >: T](x: U): Boolean = elems match { + /*inline*/ def contains[U >: T](x: U): Boolean = elems match { case null => false case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 0 @@ -180,7 +187,7 @@ class Lst[+T](val elems: Any) extends AnyVal { elem == x } - inline def foldLeft[U](z: U)(inline f: (U, T) => U) = { + /*inline*/ def foldLeft[U](z: U)(/*inline*/ f: (U, T) => U) = { def op(x: U, y: T) = f(x, y) elems match { case null => z @@ -194,7 +201,7 @@ class Lst[+T](val elems: Any) extends AnyVal { } } - inline def /: [U](z: U)(inline op: (U, T) => U) = foldLeft(z)(op) + /*inline*/ def /: [U](z: U)(/*inline*/ op: (U, T) => U) = foldLeft(z)(op) def reduceLeft[U >: T](op: (U, U) => U) = elems match { case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] @@ -255,6 +262,9 @@ class Lst[+T](val elems: Any) extends AnyVal { multi[U](newElems) } + def union [U >: T](that: Lst[U]): Lst[U] = this ++ that.filterNot(this.contains) + def intersect [U >: T](that: Lst[U]): Lst[U] = this.filter(that.contains) + def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = this.elems match { case null => Empty From 4e496474785180c10c16eac39ab17eee7829db12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 May 2018 18:36:32 +0200 Subject: [PATCH 14/18] Add firstIndexOf, takeWhile, dropWhile operations to Lst. --- compiler/src/dotty/tools/dotc/util/Lst.scala | 21 ++++++++++++++++++++ tests/run/lst/Lst.scala | 21 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/util/Lst.scala b/compiler/src/dotty/tools/dotc/util/Lst.scala index c1c5364b862f..35aaa43c883f 100644 --- a/compiler/src/dotty/tools/dotc/util/Lst.scala +++ b/compiler/src/dotty/tools/dotc/util/Lst.scala @@ -250,6 +250,27 @@ class Lst[+T](val elems: Any) extends AnyVal { def tail = drop(1) def take(n: Int): Lst[T] = slice(0, n) + def firstIndexOf(x: T): Int = elems match { + case null => 0 + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < length && elem(i) != x) i += 1 + i + case elem: T @ unchecked => if (elem == x) 0 else 1 + } + + def firstIndexWhere(p: T => Boolean): Int = elems match { + case null => 0 + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < length && !p(elem(i))) i += 1 + i + case elem: T @ unchecked => if (p(elem)) 0 else 1 + } + + def takeWhile(p: T => Boolean): Lst[T] = take(firstIndexWhere(!p(_))) + def dropWhile(p: T => Boolean): Lst[T] = drop(firstIndexWhere(!p(_))) + def ++ [U >: T](that: Lst[U]): Lst[U] = if (elems == null) that else if (that.elems == null) this diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index c1c5364b862f..35aaa43c883f 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -250,6 +250,27 @@ class Lst[+T](val elems: Any) extends AnyVal { def tail = drop(1) def take(n: Int): Lst[T] = slice(0, n) + def firstIndexOf(x: T): Int = elems match { + case null => 0 + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < length && elem(i) != x) i += 1 + i + case elem: T @ unchecked => if (elem == x) 0 else 1 + } + + def firstIndexWhere(p: T => Boolean): Int = elems match { + case null => 0 + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < length && !p(elem(i))) i += 1 + i + case elem: T @ unchecked => if (p(elem)) 0 else 1 + } + + def takeWhile(p: T => Boolean): Lst[T] = take(firstIndexWhere(!p(_))) + def dropWhile(p: T => Boolean): Lst[T] = drop(firstIndexWhere(!p(_))) + def ++ [U >: T](that: Lst[U]): Lst[U] = if (elems == null) that else if (that.elems == null) this From 2bd7192a74981aebce57a41bca66b44d60c1dea3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 May 2018 18:37:26 +0200 Subject: [PATCH 15/18] Add exists, forall, contains to Lst.Buffer --- compiler/src/dotty/tools/dotc/util/Lst.scala | 19 +++++++++++++++++++ tests/run/lst/Lst.scala | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/util/Lst.scala b/compiler/src/dotty/tools/dotc/util/Lst.scala index 35aaa43c883f..7d2276877cac 100644 --- a/compiler/src/dotty/tools/dotc/util/Lst.scala +++ b/compiler/src/dotty/tools/dotc/util/Lst.scala @@ -503,6 +503,25 @@ object Lst { this } + def exists(p: T => Boolean): Boolean = + if (len == 0) false + else if (len == 1) p(elem.asInstanceOf[T]) + else { + var i = 0 + while (i < len && !p(elems(i).asInstanceOf[T])) i += 1 + i < len + } + def forall(p: T => Boolean): Boolean = !exists(!p(_)) + + def contains(x: T): Boolean = + if (len == 0) false + else if (len == 1) elem == x + else { + var i = 0 + while (i < len && elems(i) != x) i += 1 + i < len + } + def toLst: Lst[T] = if (len == 0) Empty else if (len == 1) single(elem) diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 35aaa43c883f..7d2276877cac 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -503,6 +503,25 @@ object Lst { this } + def exists(p: T => Boolean): Boolean = + if (len == 0) false + else if (len == 1) p(elem.asInstanceOf[T]) + else { + var i = 0 + while (i < len && !p(elems(i).asInstanceOf[T])) i += 1 + i < len + } + def forall(p: T => Boolean): Boolean = !exists(!p(_)) + + def contains(x: T): Boolean = + if (len == 0) false + else if (len == 1) elem == x + else { + var i = 0 + while (i < len && elems(i) != x) i += 1 + i < len + } + def toLst: Lst[T] = if (len == 0) Empty else if (len == 1) single(elem) From 177558298519bd427c9af9dbadf2c4ddd983d601 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 May 2018 13:45:10 +0200 Subject: [PATCH 16/18] More Lst operations --- compiler/src/dotty/tools/dotc/util/Lst.scala | 68 +++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/util/Lst.scala b/compiler/src/dotty/tools/dotc/util/Lst.scala index 7d2276877cac..b14f8f3f35f1 100644 --- a/compiler/src/dotty/tools/dotc/util/Lst.scala +++ b/compiler/src/dotty/tools/dotc/util/Lst.scala @@ -14,7 +14,7 @@ import reflect.ClassTag * and the element is not an array: the element itself * Otherwise: an Array[Any] containing the elements */ -class Lst[+T](val elems: Any) extends AnyVal { +class Lst[+T](val elems: Any) extends AnyVal { self => import Lst._ def length: Int = elems match { @@ -37,6 +37,17 @@ class Lst[+T](val elems: Any) extends AnyVal { } } + /*inline*/ def foreachReversed(/*inline*/ op: T => Unit): Unit = { + def sharedOp(x: T) = op(x) + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = elems.length + while (i > 0) { i -= 1; sharedOp(elem(i)) } + case elem: T @ unchecked => sharedOp(elem) + } + } + /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ @@ -66,6 +77,28 @@ class Lst[+T](val elems: Any) extends AnyVal { result } + def toSet[U >: T]: collection.immutable.Set[U] = { + var xs = Set[U]() + foreach(xs += _) + xs + } + + def toList: List[T] = { + val buf = new collection.mutable.ListBuffer[T] + foreach(buf += _) + buf.toList + } + + def toListReversed: List[T] = { + var result: List[T] = Nil + foreach(x => result = x :: result) + result + } + + def toIterable: Iterable[T] = new Iterable[T] { + def iterator = self.iterator() + } + /** `f` is pulled out, not duplicated */ /*inline*/ def map[U](/*inline*/ f: T => U): Lst[U] = { def op(x: T) = f(x) @@ -100,6 +133,8 @@ class Lst[+T](val elems: Any) extends AnyVal { case elem: T @ unchecked => single[U](f(elem)) } + def flatMapIterable[U](f: T => Iterable[U]): Lst[U] = flatMap(x => fromIterable(f(x))) + def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { case null => Empty case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] @@ -243,29 +278,34 @@ class Lst[+T](val elems: Any) extends AnyVal { else elems match { case null => this case elems: Arr => _fromArray(elems, start, end `min` elems.length) - case elem: T @ unchecked => if (end == 0) Empty else this + case elem: T @ unchecked => if (start == 0 && end > 0) this else Empty } def drop(n: Int): Lst[T] = slice(n, length) def tail = drop(1) def take(n: Int): Lst[T] = slice(0, n) - def firstIndexOf(x: T): Int = elems match { + def firstIndexOf[U >: T](x: U, from: Int = 0): Int = elems match { case null => 0 case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 + var i = from while (i < length && elem(i) != x) i += 1 i - case elem: T @ unchecked => if (elem == x) 0 else 1 + case elem: T @ unchecked => if (from == 0 && elem == x) 0 else 1 } - def firstIndexWhere(p: T => Boolean): Int = elems match { + def firstIndexWhere(p: T => Boolean, from: Int = 0): Int = elems match { case null => 0 case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 + var i = from while (i < length && !p(elem(i))) i += 1 i - case elem: T @ unchecked => if (p(elem)) 0 else 1 + case elem: T @ unchecked => if (from == 0 && p(elem)) 0 else 1 + } + + def find(p: T => Boolean): Option[T] = { + val idx = firstIndexWhere(p) + if (idx < length) Some(apply(idx)) else None } def takeWhile(p: T => Boolean): Lst[T] = take(firstIndexWhere(!p(_))) @@ -558,4 +598,16 @@ object Lst { def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = _fromArray(elems.asInstanceOf[Arr], start, end) + + def fromIterator[T](it: Iterator[T]): Lst[T] = { + val buf = new Buffer[T] + it.foreach(buf += _) + buf.toLst + } + + def fromIterable[T](xs: Iterable[T]): Lst[T] = fromIterator(xs.iterator) + + implicit class LstDeco[T](val xs: Iterable[T]) extends AnyVal { + def toLst: Lst[T] = fromIterable(xs) + } } From 0f287f45ba464155afb35aa7b5efbeb4accdccd6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 May 2018 14:32:18 +0200 Subject: [PATCH 17/18] Let baseClasses and some derived operations return `Lst`s --- .../backend/jvm/DottyBackendInterface.scala | 15 ++- .../tools/dotc/core/CheckRealizable.scala | 3 +- .../src/dotty/tools/dotc/core/Comments.scala | 4 +- .../dotty/tools/dotc/core/Denotations.scala | 16 +++- .../tools/dotc/core/SymDenotations.scala | 92 ++++++------------- .../dotty/tools/dotc/core/TypeComparer.scala | 28 ++---- .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 18 +--- .../src/dotty/tools/dotc/core/Types.scala | 61 +++++++----- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 3 +- .../dotty/tools/dotc/transform/MixinOps.scala | 3 +- .../dotc/transform/OverridingPairs.scala | 13 +-- .../dotc/transform/PrimitiveForwarders.scala | 2 +- .../dotty/tools/dotc/transform/SymUtils.scala | 26 +++++- .../dotty/tools/dotc/typer/Implicits.scala | 6 +- .../dotty/tools/dotc/typer/RefChecks.scala | 9 +- compiler/src/dotty/tools/dotc/util/Lst.scala | 4 + tests/run/lst/Lst.scala | 68 ++++++++++++-- 18 files changed, 209 insertions(+), 164 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 32061e0d7561..e19f906ce562 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -837,17 +837,16 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma /** * All interfaces implemented by a class, except for those inherited through the superclass. - * Redundant interfaces are removed unless there is a super call to them. + * Redundant* interfaces are removed unless there is a super call to them. + * An interface is redudundant if some other interface in the set derives from it. + * TODO: write a clearer and more efficient algorithm. */ def superInterfaces: List[Symbol] = { - val directlyInheritedTraits = decorateSymbol(sym).directlyInheritedTraits - val directlyInheritedTraitsSet = directlyInheritedTraits.toSet - val allBaseClasses = directlyInheritedTraits.iterator.flatMap(_.symbol.asClass.baseClasses.drop(1)).toSet + val directlyInheritedTraits = sym.directlyInheritedTraits + def isRedundant(cls: Symbol) = directlyInheritedTraits.exists(t => (t `ne` cls) && t.derivesFrom(cls)) val superCalls = superCallsMap.getOrElse(sym, Set.empty) - val additional = (superCalls -- directlyInheritedTraitsSet).filter(_.is(Flags.Trait)) -// if (additional.nonEmpty) -// println(s"$fullName: adding supertraits $additional") - directlyInheritedTraits.filter(t => !allBaseClasses(t) || superCalls(t)) ++ additional + directlyInheritedTraits.filter(t => !isRedundant(t) || superCalls.contains(t)) ++ + (superCalls.filter(_.is(Flags.Trait)) -- directlyInheritedTraits) } /** diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index bac81942f8e3..696557d08620 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -10,6 +10,7 @@ import StdNames._ import Annotations._ import collection.mutable import ast.tpd._ +import util.Lst /** Realizability status */ object CheckRealizable { @@ -142,7 +143,7 @@ class CheckRealizable(implicit ctx: Context) { } } val baseProblems = - tp.baseClasses.map(_.baseTypeOf(tp)).flatMap(baseTypeProblems) + tp.baseClasses.map(_.baseTypeOf(tp)).flatMap(x => Lst.fromIterable(baseTypeProblems(x))) ((((Realizable: Realizability) /: memberProblems)(_ andAlso _) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 39530d718c3e..88b786750280 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -426,8 +426,8 @@ object Comments { case NoSymbol => None case _ => val searchList = - if (site.flags.is(Flags.Module)) site :: site.info.baseClasses - else site.info.baseClasses + if (site.flags.is(Flags.Module)) site :: site.info.baseClasses.toList + else site.info.baseClasses.toList searchList collectFirst { case x if defs(x) contains vble => defs(x)(vble) } match { case Some(str) if str startsWith "$" => lookupVariable(str.tail, site) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 0443f346476e..90c9bc5bf5f2 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -21,6 +21,7 @@ import printing.Printer import io.AbstractFile import config.Config import util.common._ +import util.Lst import collection.mutable.ListBuffer import Decorators.SymbolIteratorDecorator @@ -461,10 +462,17 @@ object Denotations { val sym2Accessible = sym2.isAccessibleFrom(pre) /** Does `sym1` come before `sym2` in the linearization of `pre`? */ - def precedes(sym1: Symbol, sym2: Symbol) = { - def precedesIn(bcs: List[ClassSymbol]): Boolean = bcs match { - case bc :: bcs1 => (sym1 eq bc) || !(sym2 eq bc) && precedesIn(bcs1) - case Nil => true + def precedes(sym1: Symbol, sym2: Symbol): Boolean = { + def precedesIn(bcs: Lst[ClassSymbol]): Boolean = { + var i = 0 + val len = bcs.length + while (i < len) { + val bc = bcs(i) + if (bc `eq` sym1) return true + else if (bc `eq` sym2) return false + i += 1 + } + true } (sym1 ne sym2) && (sym1.derivesFrom(sym2) || diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index af680510de14..8858e6071be9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -16,7 +16,7 @@ import ast.Trees._ import annotation.tailrec import CheckRealizable._ import util.SimpleIdentityMap -import util.Stats +import util.{Lst, Stats} import java.util.WeakHashMap import config.Config import config.Printers.noPrinter @@ -1077,10 +1077,7 @@ object SymDenotations { else overriddenFromType(owner.asClass.classInfo.selfType) private def overriddenFromType(tp: Type)(implicit ctx: Context): Iterator[Symbol] = - tp.baseClasses match { - case _ :: inherited => inherited.iterator map overriddenSymbol filter (_.exists) - case Nil => Iterator.empty - } + tp.baseClasses.iterator().drop(1).map(overriddenSymbol).filter(_.exists) /** The symbol overriding this symbol in given subclass `ofclazz`. * @@ -1095,15 +1092,16 @@ object SymDenotations { * pre: `this.owner` is in the base class sequence of `base`. */ final def superSymbolIn(base: Symbol)(implicit ctx: Context): Symbol = { - @tailrec def loop(bcs: List[ClassSymbol]): Symbol = bcs match { - case bc :: bcs1 => - val sym = matchingDecl(bcs.head, base.thisType) + val bcs = base.info.baseClasses + val len = bcs.length + @tailrec def loop(i: Int): Symbol = + if (i < len) { + val sym = matchingDecl(bcs(i), base.thisType) .suchThat(alt => !(alt is Deferred)).symbol - if (sym.exists) sym else loop(bcs.tail) - case _ => - NoSymbol - } - loop(base.info.baseClasses.dropWhile(owner != _).tail) + if (sym.exists) sym else loop(i + 1) + } + else NoSymbol + loop(bcs.firstIndexWhere(owner == _) + 1) } /** A member of class `base` is incomplete if @@ -1445,7 +1443,7 @@ object SymDenotations { override def appliedRef(implicit ctx: Context): Type = classInfo.appliedRef - private def baseData(implicit onBehalf: BaseData, ctx: Context): (List[ClassSymbol], BaseClassSet) = { + private def baseData(implicit onBehalf: BaseData, ctx: Context): (Lst[ClassSymbol], BaseClassSet) = { if (!baseDataCache.isValid) baseDataCache = BaseData.newCache() baseDataCache(this) } @@ -1453,25 +1451,31 @@ object SymDenotations { /** The base classes of this class in linearization order, * with the class itself as first element. */ - def baseClasses(implicit onBehalf: BaseData, ctx: Context): List[ClassSymbol] = + def baseClasses(implicit onBehalf: BaseData, ctx: Context): Lst[ClassSymbol] = baseData._1 /** A bitset that contains the superId's of all base classes */ private def baseClassSet(implicit onBehalf: BaseData, ctx: Context): BaseClassSet = baseData._2 - def computeBaseData(implicit onBehalf: BaseData, ctx: Context): (List[ClassSymbol], BaseClassSet) = { + def computeBaseData(implicit onBehalf: BaseData, ctx: Context): (Lst[ClassSymbol], BaseClassSet) = { def emptyParentsExpected = is(Package) || (symbol == defn.AnyClass) || ctx.erasedTypes && (symbol == defn.ObjectClass) if (classParents.isEmpty && !emptyParentsExpected) onBehalf.signalProvisional() - val builder = new BaseDataBuilder + val builder = new Lst.Buffer[ClassSymbol] for (p <- classParents) p.classSymbol match { - case pcls: ClassSymbol => builder.addAll(pcls.baseClasses) - case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") + case pcls: ClassSymbol => + pcls.baseClasses.foreachReversed { bc => + if (!builder.contains(bc)) builder += bc + } + case _ => + assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") } - (classSymbol :: builder.baseClasses, builder.baseClassSet) + val bcs = BaseClassSet(builder.toLst) + builder += classSymbol + (builder.toLst.reverse, bcs) } final override def derivesFrom(base: Symbol)(implicit ctx: Context): Boolean = @@ -2038,7 +2042,7 @@ object SymDenotations { */ trait BaseData extends InheritedCache { def apply(clsd: ClassDenotation) - (implicit onBehalf: BaseData, ctx: Context): (List[ClassSymbol], BaseClassSet) + (implicit onBehalf: BaseData, ctx: Context): (Lst[ClassSymbol], BaseClassSet) def signalProvisional(): Unit } @@ -2123,7 +2127,7 @@ object SymDenotations { } private class BaseDataImpl(createdAt: Period) extends InheritedCacheImpl(createdAt) with BaseData { - private[this] var cache: (List[ClassSymbol], BaseClassSet) = null + private[this] var cache: (Lst[ClassSymbol], BaseClassSet) = null private[this] var valid = true private[this] var locked = false @@ -2141,7 +2145,7 @@ object SymDenotations { def signalProvisional() = provisional = true def apply(clsd: ClassDenotation)(implicit onBehalf: BaseData, ctx: Context) - : (List[ClassSymbol], BaseClassSet) = { + : (Lst[ClassSymbol], BaseClassSet) = { assert(isValid) try { if (cache != null) cache @@ -2181,49 +2185,9 @@ object SymDenotations { } object BaseClassSet { - def apply(bcs: List[ClassSymbol]): BaseClassSet = + def apply(bcs: Lst[ClassSymbol]): BaseClassSet = new BaseClassSet(bcs.toArray.map(_.id)) } - /** A class to combine base data from parent types */ - class BaseDataBuilder { - private[this] var classes: List[ClassSymbol] = Nil - private[this] var classIds = new Array[Int](32) - private[this] var length = 0 - - private def resize(size: Int) = { - val classIds1 = new Array[Int](size) - Array.copy(classIds, 0, classIds1, 0, classIds.length min size) - classIds = classIds1 - } - - private def add(sym: Symbol): Unit = { - if (length == classIds.length) resize(length * 2) - classIds(length) = sym.id - length += 1 - } - - def addAll(bcs: List[ClassSymbol]): this.type = { - val len = length - bcs match { - case bc :: bcs1 => - addAll(bcs1) - if (!new BaseClassSet(classIds).contains(bc, len)) { - add(bc) - classes = bc :: classes - } - case nil => - } - this - } - - def baseClassSet = { - if (length != classIds.length) resize(length) - new BaseClassSet(classIds) - } - - def baseClasses: List[ClassSymbol] = classes - } - @sharable private[this] var indent = 0 // for completions printing } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d2e122b7bfe8..4da3b9193e86 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -11,6 +11,7 @@ import config.Config import config.Printers.{typr, constr, subtyping, gadts, noPrinter} import TypeErasure.{erasedLub, erasedGlb} import TypeApplications._ +import util.Lst import scala.util.control.NonFatal import reporting.trace @@ -751,14 +752,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1w => tp1w.typeSymbol.isClass && { val classBounds = tycon2.classSymbols - def liftToBase(bcs: List[ClassSymbol]): Boolean = bcs match { - case bc :: bcs1 => - classBounds.exists(bc.derivesFrom) && appOK(tp1w.baseType(bc)) || - liftToBase(bcs1) - case _ => - false - } - liftToBase(tp1w.baseClasses) + // TODO: Try to use testLifted + tp1w.baseClasses.exists(bc => + classBounds.exists(bc.derivesFrom) && appOK(tp1w.baseType(bc))) } || fourthTry } @@ -924,17 +920,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { */ private def testLifted(tp1: Type, tp2: Type, tparams: List[TypeParamInfo], p: Type => Boolean): Boolean = { val classBounds = tp2.classSymbols - def recur(bcs: List[ClassSymbol]): Boolean = bcs match { - case bc :: bcs1 => - (classBounds.exists(bc.derivesFrom) && - variancesConform(bc.typeParams, tparams) && - p(tp1.baseType(bc)) - || - recur(bcs1)) - case nil => - false - } - recur(tp1.baseClasses) + tp1.baseClasses.exists(bc => + classBounds.exists(bc.derivesFrom) && + variancesConform(bc.typeParams, tparams) && + p(tp1.baseType(bc)) + ) } /** Replace any top-level recursive type `{ z => T }` in `tp` with diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index a4566e729b72..d729b4999b5b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -264,7 +264,7 @@ object TypeErasure { } // We are not interested in anything that is not a supertype of tp2 - val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom) + val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom).toList // From the spec, "Linearization also satisfies the property that a // linearization of a class always contains the linearization of its diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1ff312dae475..f4be146fb89e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -13,7 +13,8 @@ import StdNames._ import Annotations._ import annotation.tailrec import config.Config -import util.Property +import util.{Property, Lst} +import transform.SymUtils.dominators import collection.mutable import ast.tpd._ import reporting.trace @@ -128,22 +129,12 @@ trait TypeOps { this: Context => // TODO: Make standalone object. def orDominator(tp: Type): Type = { /** a faster version of cs1 intersect cs2 */ - def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { + def intersect(cs1: Lst[ClassSymbol], cs2: Lst[ClassSymbol]): Lst[ClassSymbol] = { val cs2AsSet = new util.HashSet[ClassSymbol](128) cs2.foreach(cs2AsSet.addEntry) cs1.filter(cs2AsSet.contains) } - /** The minimal set of classes in `cs` which derive all other classes in `cs` */ - def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match { - case c :: rest => - val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu - if (cs == c.baseClasses) accu1 else dominators(rest, accu1) - case Nil => // this case can happen because after erasure we do not have a top class anymore - assert(ctx.erasedTypes || ctx.reporter.errorsReported) - defn.ObjectClass :: Nil - } - def mergeRefinedOrApplied(tp1: Type, tp2: Type): Type = { def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2") tp1 match { @@ -197,7 +188,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. err case _ => val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) - val doms = dominators(commonBaseClasses, Nil) + val doms = dominators(commonBaseClasses) + println(i"dominators $tp / $commonBaseClasses = $doms") def baseTp(cls: ClassSymbol): Type = tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied) doms.map(baseTp).reduceLeft(AndType.apply) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ec569c22784f..395fdc3869ac 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -422,13 +422,13 @@ object Types { * Inherited by all type proxies. Overridden for And and Or types. * `Nil` for all other types. */ - def baseClasses(implicit ctx: Context): List[ClassSymbol] = track("baseClasses") { + def baseClasses(implicit ctx: Context): Lst[ClassSymbol] = track("baseClasses") { this match { case tp: TypeProxy => tp.underlying.baseClasses case tp: ClassInfo => tp.cls.baseClasses - case _ => Nil + case _ => Lst() } } @@ -2440,21 +2440,28 @@ object Types { abstract case class AndType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType { private[this] var myBaseClassesPeriod: Period = Nowhere - private[this] var myBaseClasses: List[ClassSymbol] = _ - /** Base classes of are the merge of the operand base classes. */ - override final def baseClasses(implicit ctx: Context) = { + private[this] var myBaseClasses: Lst[ClassSymbol] = _ + + /** Base classes of and types are the merge of the operand base classes. */ + override final def baseClasses(implicit ctx: Context): Lst[ClassSymbol] = { if (myBaseClassesPeriod != ctx.period) { val bcs1 = tp1.baseClasses + val bcs2 = tp2.baseClasses val bcs1set = BaseClassSet(bcs1) - def recur(bcs2: List[ClassSymbol]): List[ClassSymbol] = bcs2 match { - case bc2 :: bcs2rest => - if (bcs1set contains bc2) - if (bc2.is(Trait)) recur(bcs2rest) - else bcs1 // common class, therefore rest is the same in both sequences - else bc2 :: recur(bcs2rest) - case nil => bcs1 + val buf = new Lst.Buffer[ClassSymbol] + var i = 0 + while (i < bcs2.length) { + val bc2 = bcs2(i) + if (bcs1set `contains` bc2) + if (bc2.is(Trait)) i += 1 + else i = bcs2.length // we have a common class, therefore rest is the same in both sequences + else { + buf += bc2 + i += 1 + } } - myBaseClasses = recur(tp2.baseClasses) + buf ++= bcs1 + myBaseClasses = buf.toLst myBaseClassesPeriod = ctx.period } myBaseClasses @@ -2504,22 +2511,26 @@ object Types { abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType { private[this] var myBaseClassesPeriod: Period = Nowhere - private[this] var myBaseClasses: List[ClassSymbol] = _ - /** Base classes of are the intersection of the operand base classes. */ - override final def baseClasses(implicit ctx: Context) = { + private[this] var myBaseClasses: Lst[ClassSymbol] = _ + /** Base classes are the intersection of the operand base classes. */ + override final def baseClasses(implicit ctx: Context): Lst[ClassSymbol] = { if (myBaseClassesPeriod != ctx.period) { val bcs1 = tp1.baseClasses + val bcs2 = tp2.baseClasses val bcs1set = BaseClassSet(bcs1) - def recur(bcs2: List[ClassSymbol]): List[ClassSymbol] = bcs2 match { - case bc2 :: bcs2rest => - if (bcs1set contains bc2) - if (bc2.is(Trait)) bc2 :: recur(bcs2rest) - else bcs2 - else recur(bcs2rest) - case nil => - bcs2 + val buf = new Lst.Buffer[ClassSymbol] + var i = 0 + while (i < bcs2.length) { + val bc2 = bcs2(i) + if (bcs1set `contains` bc2) { + buf += bc2 + i += 1 + if (!bc2.is(Trait)) // common class, rest is shared + while (i < bcs2.length) { buf += bcs2(i); i += 1 } + } + else i += 1 } - myBaseClasses = recur(tp2.baseClasses) + myBaseClasses = buf.toLst myBaseClassesPeriod = ctx.period } myBaseClasses diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 3bcf99e8b3cc..f55bfafced27 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -275,6 +275,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // We cannot filter out `LegacyApp` because it contains the main method, // see the comment about main class discovery in `computeType`. .filter(bc => !bc.is(Scala2x) || bc.eq(LegacyAppClass)) + .toList .flatMap(_.classInfo.decls.filter(s => !(s.is(Private) || declSet.contains(s)))) // Inherited members need to be computed lazily because a class might contain // itself as an inherited member, like in `class A { class B extends A }`, @@ -287,7 +288,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder def linearizedAncestorTypes(info: ClassInfo): List[Type] = { val ref = info.appliedRef // Note that the ordering of classes in `baseClasses` is important. - info.baseClasses.tail.map(ref.baseType) + info.baseClasses.tail.map(ref.baseType).toList } // The hash generated by sbt for definitions is supposed to be symmetric so diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index d3e08d7f9b7e..82ff877b554c 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -7,6 +7,7 @@ import util.Positions._ import SymUtils._ import StdNames._, NameOps._ import Decorators._ +import util.Lst class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Context) { import ast.tpd._ @@ -101,7 +102,7 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont superRef(target).appliedToTypes(targs).appliedToArgss(vrefss) private def competingMethodsIterator(meth: Symbol): Iterator[Symbol] = { - cls.baseClasses.iterator + cls.baseClasses.iterator() .filter(_ ne meth.owner) .map(meth.overriddenSymbol) .filter(_.exists) diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 57858600dad6..651efcc699a4 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -3,7 +3,7 @@ package transform import core._ import Flags._, Symbols._, Contexts._, Types._, Scopes._, Decorators._ -import util.HashSet +import util.{HashSet, Lst} import collection.mutable import collection.immutable.BitSet import typer.ErrorReporting.cyclicErrorMsg @@ -48,17 +48,14 @@ object OverridingPairs { private val decls = { val decls = newScope // fill `decls` with overriding shadowing overridden */ - def fillDecls(bcs: List[Symbol], deferred: Boolean): Unit = bcs match { - case bc :: bcs1 => - fillDecls(bcs1, deferred) + def fillDecls(bcs: Lst[Symbol], deferred: Boolean): Unit = + bcs.foreachReversed { bc => var e = bc.info.decls.lastEntry while (e != null) { - if (e.sym.is(Deferred) == deferred && !exclude(e.sym)) - decls.enter(e.sym) + if (e.sym.is(Deferred) == deferred && !exclude(e.sym)) decls.enter(e.sym) e = e.prev } - case nil => - } + } // first, deferred (this will need to change if we change lookup rules! fillDecls(base.info.baseClasses, deferred = true) // then, concrete. diff --git a/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala b/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala index 853c74c815c9..9ea4ee1956db 100644 --- a/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala +++ b/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala @@ -45,7 +45,7 @@ class PrimitiveForwarders extends MiniPhase with IdentityDenotTransformer { this import ops._ def methodPrimitiveForwarders: List[Tree] = - for (meth <- mixins.flatMap(_.info.decls.toList.flatMap(needsPrimitiveForwarderTo)).distinct) + for (meth <- mixins.toList.flatMap(_.info.decls.toList.flatMap(needsPrimitiveForwarderTo)).distinct) yield polyDefDef(implementation(meth.asTerm), forwarder(meth)) cpy.Template(impl)(body = methodPrimitiveForwarders ::: impl.body) diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index aabd341533e2..c0dc87576d0c 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -13,6 +13,7 @@ import NameOps._ import NameKinds._ import Flags._ import Annotations._ +import util.Lst import language.implicitConversions import scala.annotation.tailrec @@ -20,6 +21,29 @@ import scala.annotation.tailrec object SymUtils { implicit def decorateSymbol(sym: Symbol): SymUtils = new SymUtils(sym) implicit def decorateSymDenot(d: SymDenotation): SymUtils = new SymUtils(d.symbol) + + /** The minimal subsequence of classes in `cs` such that every other class in `cs` is a superclass + * of a class in the dominator. + * @pre earlier classes in `cs` can derive from later ones, but not vice versa. + */ + def dominators(cs: Lst[ClassSymbol])(implicit ctx: Context): Lst[ClassSymbol] = { + val buf = new Lst.Buffer[ClassSymbol] + val len = cs.length + var i = 0 + while (i < len) { + val c = cs(i) + if (!buf.exists(_.derivesFrom(c))) buf += c + val bcs = c.baseClasses + val delta = i + i += 1 + while (i < cs.length && i - delta < bcs.length && cs(i) == bcs(i - delta)) i += 1 + } + if (buf.isEmpty) { // this case can happen because after erasure we do not have a top class anymore + assert(ctx.erasedTypes || ctx.reporter.errorsReported) + buf += defn.ObjectClass + } + buf.toLst + } } /** A decorator that provides methods on symbols @@ -33,7 +57,7 @@ class SymUtils(val self: Symbol) extends AnyVal { val superCls = self.asClass.superClass val baseClasses = self.asClass.baseClasses if (baseClasses.isEmpty) Nil - else baseClasses.tail.takeWhile(_ ne superCls).reverse + else baseClasses.slice(1, baseClasses.firstIndexWhere(_ `eq` superCls)).reverse.toList } /** All traits implemented by a class, except for those inherited through the superclass. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index bb7f707f1c23..48bea2c72d07 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1175,8 +1175,9 @@ class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { else { val size = typeSize(proto) val csyms = proto.classSymbols - def updateMap(idx: Int, seen: Map[ClassSymbol, Int]): SearchHistory = - if (idx < csyms.length) { + def updateMap(idx: Int, seen: Map[ClassSymbol, Int]): SearchHistory = { + val len = csyms.length + if (idx < len) { val csym = csyms(idx) seen.get(csym) match { // proto complexity is >= than the last time it was seen → diverge @@ -1186,6 +1187,7 @@ class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { } else new SearchHistory(searchDepth + 1, seen) + } if (csyms.isEmpty) this else updateMap(0, seen) } } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 628c069d99d5..8b9ffb4784e8 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -690,11 +690,10 @@ object RefChecks { // by `Super`. So we cannot pretend they have a different type when seen from `Sub`. def checkParameterizedTraitsOK() = { val mixins = clazz.mixins - for { - cls <- clazz.info.baseClasses.tail - if cls.paramAccessors.nonEmpty && !mixins.contains(cls) - problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, "parameterized", "super") - } ctx.error(problem(), clazz.pos) + for (cls <- clazz.info.baseClasses) + if ((cls `ne` clazz) && cls.paramAccessors.nonEmpty && !mixins.contains(cls)) + for (problem <- variantInheritanceProblems(cls, clazz.asClass.superClass, "parameterized", "super")) + ctx.error(problem(), clazz.pos) } checkParameterizedTraitsOK() diff --git a/compiler/src/dotty/tools/dotc/util/Lst.scala b/compiler/src/dotty/tools/dotc/util/Lst.scala index b14f8f3f35f1..0c4fe2621bad 100644 --- a/compiler/src/dotty/tools/dotc/util/Lst.scala +++ b/compiler/src/dotty/tools/dotc/util/Lst.scala @@ -239,6 +239,8 @@ class Lst[+T](val elems: Any) extends AnyVal { self => /*inline*/ def /: [U](z: U)(/*inline*/ op: (U, T) => U) = foldLeft(z)(op) def reduceLeft[U >: T](op: (U, U) => U) = elems match { + case null => + throw new UnsupportedOperationException("Lst.Empty.reduceLeft") case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 1 var acc: U = elem(0) @@ -516,6 +518,8 @@ object Lst { elems = newElems } + def isEmpty = size == 0 + def += (x: T): this.type = { if (len == 0) elem = x else { diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 7d2276877cac..d1c73698a56a 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -14,7 +14,7 @@ import reflect.ClassTag * and the element is not an array: the element itself * Otherwise: an Array[Any] containing the elements */ -class Lst[+T](val elems: Any) extends AnyVal { +class Lst[+T](val elems: Any) extends AnyVal { self => import Lst._ def length: Int = elems match { @@ -37,6 +37,17 @@ class Lst[+T](val elems: Any) extends AnyVal { } } + /*inline*/ def foreachReversed(/*inline*/ op: T => Unit): Unit = { + def sharedOp(x: T) = op(x) + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = elems.length + while (i > 0) { i -= 1; sharedOp(elem(i)) } + case elem: T @ unchecked => sharedOp(elem) + } + } + /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ @@ -66,6 +77,28 @@ class Lst[+T](val elems: Any) extends AnyVal { result } + def toSet[U >: T]: collection.immutable.Set[U] = { + var xs = Set[U]() + foreach(xs += _) + xs + } + + def toList: List[T] = { + val buf = new collection.mutable.ListBuffer[T] + foreach(buf += _) + buf.toList + } + + def toListReversed: List[T] = { + var result: List[T] = Nil + foreach(x => result = x :: result) + result + } + + def toIterable: Iterable[T] = new Iterable[T] { + def iterator = self.iterator() + } + /** `f` is pulled out, not duplicated */ /*inline*/ def map[U](/*inline*/ f: T => U): Lst[U] = { def op(x: T) = f(x) @@ -100,6 +133,8 @@ class Lst[+T](val elems: Any) extends AnyVal { case elem: T @ unchecked => single[U](f(elem)) } + def flatMapIterable[U](f: T => Iterable[U]): Lst[U] = flatMap(f.andThen(fromIterable)) + def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { case null => Empty case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] @@ -243,29 +278,34 @@ class Lst[+T](val elems: Any) extends AnyVal { else elems match { case null => this case elems: Arr => _fromArray(elems, start, end `min` elems.length) - case elem: T @ unchecked => if (end == 0) Empty else this + case elem: T @ unchecked => if (start == 0 && end > 0) this else Empty } def drop(n: Int): Lst[T] = slice(n, length) def tail = drop(1) def take(n: Int): Lst[T] = slice(0, n) - def firstIndexOf(x: T): Int = elems match { + def firstIndexOf[U >: T](x: U, from: Int = 0): Int = elems match { case null => 0 case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 + var i = from while (i < length && elem(i) != x) i += 1 i - case elem: T @ unchecked => if (elem == x) 0 else 1 + case elem: T @ unchecked => if (from == 0 && elem == x) 0 else 1 } - def firstIndexWhere(p: T => Boolean): Int = elems match { + def firstIndexWhere(p: T => Boolean, from: Int = 0): Int = elems match { case null => 0 case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 + var i = from while (i < length && !p(elem(i))) i += 1 i - case elem: T @ unchecked => if (p(elem)) 0 else 1 + case elem: T @ unchecked => if (from == 0 && p(elem)) 0 else 1 + } + + def find(p: T => Boolean): Option[T] = { + val idx = firstIndexWhere(p) + if (idx < length) Some(apply(idx)) else None } def takeWhile(p: T => Boolean): Lst[T] = take(firstIndexWhere(!p(_))) @@ -558,4 +598,16 @@ object Lst { def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = _fromArray(elems.asInstanceOf[Arr], start, end) + + def fromIterator[T](it: Iterator[T]): Lst[T] = { + val buf = new Buffer[T] + it.foreach(buf += _) + buf.toLst + } + + def fromIterable[T](xs: Iterable[T]): Lst[T] = fromIterator(xs.iterator) + + implicit class LstDeco[T](val xs: Iterable[T]) extends AnyVal { + def toLst: Lst[T] = fromIterable(xs) + } } From 5fde3dbd4cf661289da7a9c282936dabed2889d9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 May 2018 18:09:03 +0200 Subject: [PATCH 18/18] More List -> Lst rewrites Mostly in TypeErasure and MixinOps, plus derived code. Also: remove stray println. --- .../backend/jvm/DottyBackendInterface.scala | 4 ++-- .../src/dotty/tools/dotc/core/TypeErasure.scala | 16 +++++++--------- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 1 - .../src/dotty/tools/dotc/transform/Mixin.scala | 2 +- .../dotty/tools/dotc/transform/MixinOps.scala | 2 +- .../tools/dotc/transform/ResolveSuper.scala | 2 +- .../dotty/tools/dotc/transform/SymUtils.scala | 10 +++++----- 7 files changed, 17 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index e19f906ce562..0349f75d40bb 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -845,8 +845,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma val directlyInheritedTraits = sym.directlyInheritedTraits def isRedundant(cls: Symbol) = directlyInheritedTraits.exists(t => (t `ne` cls) && t.derivesFrom(cls)) val superCalls = superCallsMap.getOrElse(sym, Set.empty) - directlyInheritedTraits.filter(t => !isRedundant(t) || superCalls.contains(t)) ++ - (superCalls.filter(_.is(Flags.Trait)) -- directlyInheritedTraits) + directlyInheritedTraits.filter(t => !isRedundant(t) || superCalls.contains(t)).toList ++ + superCalls.filter(cls => cls.is(Flags.Trait) && !directlyInheritedTraits.contains(cls)) } /** diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index d729b4999b5b..e8f031334eaf 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -264,13 +264,14 @@ object TypeErasure { } // We are not interested in anything that is not a supertype of tp2 - val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom).toList + val tp2superclasses = tp1.baseClasses.filter(cls2.derivesFrom) // From the spec, "Linearization also satisfies the property that a // linearization of a class always contains the linearization of its - // direct superclass as a suffix"; it's enought to consider every + // direct superclass as a suffix"; it's enough to consider every // candidate up to the first class. - val candidates = takeUntil(tp2superclasses)(!_.is(Trait)) + val firstRealClassIdx = tp2superclasses.firstIndexWhere(!_.is(Trait)) + val candidates = tp2superclasses.take(firstRealClassIdx + 1) // Candidates st "no other common superclass or trait derives from S" val minimums = candidates.filter { cand => @@ -278,12 +279,9 @@ object TypeErasure { } // Pick the last minimum to prioritise classes over traits - minimums.lastOption match { - case Some(lub) if lub != defn.AnyClass && lub != defn.AnyValClass => - lub.typeRef - case _ => // Any/AnyVal only exist before erasure - defn.ObjectType - } + val lub = if (minimums.isEmpty) defn.ObjectClass else minimums.last + if (lub == defn.AnyClass || lub == defn.AnyValClass) defn.ObjectType + else lub.typeRef } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index f4be146fb89e..ce489f42f3ad 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -189,7 +189,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case _ => val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) val doms = dominators(commonBaseClasses) - println(i"dominators $tp / $commonBaseClasses = $doms") def baseTp(cls: ClassSymbol): Type = tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied) doms.map(baseTp).reduceLeft(AndType.apply) diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 437a8a45af3c..0401a7537a2f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -259,7 +259,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => body = if (cls is Trait) traitDefs(impl.body) else { - val mixInits = mixins.flatMap { mixin => + val mixInits = mixins.toList.flatMap { mixin => flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin) } superCallOpt(superCls) ::: mixInits ::: impl.body diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index 82ff877b554c..ce2de53aa742 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -13,7 +13,7 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont import ast.tpd._ val superCls: Symbol = cls.superClass - val mixins: List[ClassSymbol] = cls.mixins + val mixins: Lst[ClassSymbol] = cls.mixins lazy val JUnit4Annotations: List[Symbol] = List("Test", "Ignore", "Before", "After", "BeforeClass", "AfterClass"). map(n => ctx.getClassIfDefined("org.junit." + n)). diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 940ce9199a08..55e965f8ac3f 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -74,7 +74,7 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = polyDefDef(implementation(meth.asTerm), forwarder(meth)) } - val overrides = mixins.flatMap(mixin => superAccessors(mixin) ::: methodOverrides(mixin)) + val overrides = mixins.toList.flatMap(mixin => superAccessors(mixin) ::: methodOverrides(mixin)) cpy.Template(impl)(body = overrides ::: impl.body) } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index c0dc87576d0c..840cda54e303 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -53,18 +53,18 @@ class SymUtils(val self: Symbol) extends AnyVal { import SymUtils._ /** All traits implemented by a class or trait except for those inherited through the superclass. */ - def directlyInheritedTraits(implicit ctx: Context): List[ClassSymbol] = { + def directlyInheritedTraits(implicit ctx: Context): Lst[ClassSymbol] = { val superCls = self.asClass.superClass val baseClasses = self.asClass.baseClasses - if (baseClasses.isEmpty) Nil - else baseClasses.slice(1, baseClasses.firstIndexWhere(_ `eq` superCls)).reverse.toList + if (baseClasses.isEmpty) Lst() + else baseClasses.slice(1, baseClasses.firstIndexWhere(_ `eq` superCls)).reverse } /** All traits implemented by a class, except for those inherited through the superclass. * The empty list if `self` is a trait. */ - def mixins(implicit ctx: Context): List[ClassSymbol] = { - if (self is Trait) Nil + def mixins(implicit ctx: Context): Lst[ClassSymbol] = { + if (self is Trait) Lst() else directlyInheritedTraits }