From 609acf30b2999869098c39c092d8728e841b836d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 May 2018 10:30:35 +0200 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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