diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index a1effc33ad51..2115a332eba4 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -70,7 +70,7 @@ class Driver { val summary = CompilerCommand.distill(args)(using ictx) ictx.setSettings(summary.sstate) MacroClassLoader.init(ictx) - Positioned.updateDebugPos(using ictx) + Positioned.init(using ictx) inContext(ictx) { if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 835ce678bdb6..47dbd1826d4c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -19,23 +19,26 @@ import java.io.{ PrintWriter } /** A base class for things that have positions (currently: modifiers and trees) */ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends SrcPos, Product, Cloneable { + import Positioned.{ids, nextId, debugId} - private var myUniqueId: Int = _ private var mySpan: Span = _ - /** A unique identifier. Among other things, used for determining the source file - * component of the position. + /** A unique identifier in case -Yshow-tree-ids, or -Ydebug-tree-with-id + * is set, -1 otherwise. */ - def uniqueId: Int = myUniqueId + def uniqueId: Int = + if ids != null && ids.containsKey(this) then ids.get(this) else -1 - def uniqueId_=(id: Int): Unit = { - def printTrace() = { - println(s"Debug tree (id=${Positioned.debugId}) creation \n$this\n") - Reporter.displayPrompt(Console.in, new PrintWriter(Console.err, true)) - } - if (Positioned.debugId == id) printTrace() - myUniqueId = id - } + private def allocateId() = + if ids != null then + val ownId = nextId + nextId += 1 + ids.put(this, ownId) + if ownId == debugId then + println(s"Debug tree (id=$debugId) creation \n$this\n") + Reporter.displayPrompt(Console.in, new PrintWriter(Console.err, true)) + + allocateId() /** The span part of the item's position */ def span: Span = mySpan @@ -43,10 +46,9 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src def span_=(span: Span): Unit = mySpan = span - uniqueId = src.nextId span = envelope(src) - def source: SourceFile = SourceFile.fromId(uniqueId) + val source: SourceFile = src def sourcePos(using Context): SourcePosition = source.atSpan(span) /** This positioned item, widened to `SrcPos`. Used to make clear we only need the @@ -125,7 +127,7 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src /** Clone this node but assign it a fresh id which marks it as a node in `file`. */ def cloneIn(src: SourceFile): this.type = { val newpd: this.type = clone.asInstanceOf[this.type] - newpd.uniqueId = src.nextId + newpd.allocateId() // assert(newpd.uniqueId != 2208, s"source = $this, ${this.uniqueId}, ${this.span}") newpd } @@ -237,8 +239,14 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Src } object Positioned { - @sharable private[Positioned] var debugId = Int.MinValue + @sharable private var debugId = Int.MinValue + @sharable private var ids: java.util.WeakHashMap[Positioned, Int] = null + @sharable private var nextId: Int = 0 - def updateDebugPos(using Context): Unit = + def init(using Context): Unit = debugId = ctx.settings.YdebugTreeWithId.value + if ids == null && ctx.settings.YshowTreeIds.value + || debugId != ctx.settings.YdebugTreeWithId.default + then + ids = java.util.WeakHashMap() } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index ec85fbdb766b..b27969a202a8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -221,7 +221,7 @@ object Trees { } } - override def hashCode(): Int = uniqueId // for debugging; was: System.identityHashCode(this) + override def hashCode(): Int = System.identityHashCode(this) override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala index 41e3db6dc69f..268b02b4389f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala @@ -8,7 +8,6 @@ import dotty.tools.tasty.TastyBuffer import TastyBuffer.{Addr, NoAddr, AddrWidth} import util.Util.bestFit -import util.SparseIntArray import config.Printers.pickling import ast.untpd.Tree @@ -21,23 +20,20 @@ class TreeBuffer extends TastyBuffer(50000) { private var delta: Array[Int] = _ private var numOffsets = 0 - /** A map from tree unique ids to the address index at which a tree is pickled. - * Note that trees are looked up by reference equality, - * so one can reliably use this function only directly after `pickler`. - */ - private val addrOfTree = SparseIntArray() + /** A map from trees to the address at which a tree is pickled. */ + private val treeAddrs = util.IntMap[Tree](initialCapacity = 8192) def registerTreeAddr(tree: Tree): Addr = - val id = tree.uniqueId - if addrOfTree.contains(id) then Addr(addrOfTree(id)) - else - addrOfTree(tree.uniqueId) = currentAddr.index + val idx = treeAddrs(tree) + if idx < 0 then + treeAddrs(tree) = currentAddr.index currentAddr + else + Addr(idx) def addrOfTree(tree: Tree): Addr = - val idx = tree.uniqueId - if addrOfTree.contains(idx) then Addr(addrOfTree(idx)) - else NoAddr + val idx = treeAddrs(tree) + if idx < 0 then NoAddr else Addr(idx) private def offset(i: Int): Addr = Addr(offsets(i)) @@ -163,7 +159,10 @@ class TreeBuffer extends TastyBuffer(50000) { } def adjustTreeAddrs(): Unit = - addrOfTree.transform((id, addr) => adjusted(Addr(addr)).index) + var i = 0 + while i < treeAddrs.size do + treeAddrs.setValue(i, adjusted(Addr(treeAddrs.value(i))).index) + i += 1 /** Final assembly, involving the following steps: * - compute deltas diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 00db8df2a6e0..c8ce76db64d9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -70,8 +70,6 @@ object Inliner { * and body that replace it. */ def inlineCall(tree: Tree)(using Context): Tree = { - val startId = ctx.source.nextId - if tree.symbol.denot != SymDenotations.NoDenotation && tree.symbol.owner.companionModule == defn.CompiletimeTestingPackageObject then @@ -136,10 +134,6 @@ object Inliner { |You can use ${setting.name} to change the limit.""", (tree :: enclosingInlineds).last.srcPos ) - - val endId = ctx.source.nextId - addInlinedTrees(endId - startId) - tree2 } @@ -735,6 +729,26 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case _ => } + /** The number of nodes in this tree, excluding code in nested inline + * calls and annotations of definitions. + */ + def treeSize(x: Any): Int = + var siz = 0 + x match + case x: Trees.Inlined[_] => + case x: Positioned => + var i = 0 + while i < x.productArity do + siz += treeSize(x.productElement(i)) + i += 1 + case x: List[_] => + var xs = x + while xs.nonEmpty do + siz += treeSize(xs.head) + xs = xs.tail + case _ => + siz + trace(i"inlining $call", inlining, show = true) { // The normalized bindings collected in `bindingsBuf` @@ -758,6 +772,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { if (inlinedMethod == defn.Compiletime_error) issueError() + addInlinedTrees(treeSize(finalExpansion)) + // Take care that only argument bindings go into `bindings`, since positions are // different for bindings from arguments and bindings from body. tpd.Inlined(call, finalBindings, finalExpansion) diff --git a/compiler/src/dotty/tools/dotc/util/IntMap.scala b/compiler/src/dotty/tools/dotc/util/IntMap.scala new file mode 100644 index 000000000000..008ea866f70e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/IntMap.scala @@ -0,0 +1,57 @@ +package dotty.tools.dotc.util + +/** A dense map from some `Key` type to `Int. Dense means: All keys and values + * are stored in arrays from 0 up to the size of the map. Keys and values + * can be obtained by index using `key(index)` and `value(index)`. Values + * can also be stored using `setValue(index, value)`. + * + * ome privileged protected access to its internals + * @param initialCapacity Indicates the initial number of slots in the hash table. + * The actual number of slots is always a power of 2, so the + * initial size of the table will be the smallest power of two + * that is equal or greater than the given `initialCapacity`. + * Minimum value is 4. + * @param capacityMultiple The minimum multiple of capacity relative to used elements. + * The hash table will be re-sized once the number of elements + * multiplied by capacityMultiple exceeds the current size of the hash table. + * However, a table of size up to DenseLimit will be re-sized only + * once the number of elements reaches the table's size. + */ +final class IntMap[Key](initialCapacity: Int = 8, capacityMultiple: Int = 2) +extends PerfectHashing[Key](initialCapacity, capacityMultiple): + private var values: Array[Int] = _ + + def default: Int = -1 + + protected override def allocate(capacity: Int) = + super.allocate(capacity) + values = new Array[Int](capacity) + + /** The value associated with key `k`, or else `default`. */ + def apply(k: Key): Int = + val idx = index(k) + if idx < 0 then default else values(idx) + + /** Associate key `k` with value `v` */ + def update(k: Key, v: Int): Unit = + val idx = add(k) // don't merge the two statements, `add` might change `values`. + values(idx) = v + + protected override def growTable() = + val oldValues = values + super.growTable() + Array.copy(oldValues, 0, values, 0, oldValues.length) + + def valuesIterator = values.iterator.take(size) + + def iterator: Iterator[(Key, Int)] = keysIterator.zip(valuesIterator) + + /** The value stored at index `i` */ + def value(i: Int) = values(i) + + /** Change the value stored at index `i` to `v` */ + def setValue(i: Int, v: Int) = values(i) = v + + override def toString = + iterator.map((k, v) => s"$k -> $v").mkString("IntMap(", ", ", ")") +end IntMap diff --git a/compiler/src/dotty/tools/dotc/util/PerfectHashing.scala b/compiler/src/dotty/tools/dotc/util/PerfectHashing.scala new file mode 100644 index 000000000000..fca790837959 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/PerfectHashing.scala @@ -0,0 +1,136 @@ +package dotty.tools.dotc.util + +object PerfectHashing: + + /** The number of elements up to which dense packing is used. + * If the number of elements reaches `DenseLimit` a hash table is used instead + */ + inline val DenseLimit = 16 + +/** A map that maps keys to unique integers in a dense interval starting at 0. + * @param initialCapacity Indicates the initial number of slots in the hash table. + * The actual number of slots is always a power of 2, so the + * initial size of the table will be the smallest power of two + * that is equal or greater than the given `initialCapacity`. + * Minimum value is 4. + * @param capacityMultiple The minimum multiple of capacity relative to used elements. + * The hash table will be re-sized once the number of elements + * multiplied by capacityMultiple exceeds the current size of the hash table. + * However, a table of size up to DenseLimit will be re-sized only + * once the number of elements reaches the table's size. + */ +class PerfectHashing[Key](initialCapacity: Int = 8, capacityMultiple: Int = 2): + import PerfectHashing.DenseLimit + + private var used: Int = _ + private var table: Array[Int] = _ + private var keys: Array[AnyRef] = _ + + clear() + + protected def allocate(capacity: Int) = + keys = new Array[AnyRef](capacity) + if !isDense then + table = new Array[Int](capacity * roundToPower(capacityMultiple)) + + private def roundToPower(n: Int) = + if Integer.bitCount(n) == 1 then n + else 1 << (32 - Integer.numberOfLeadingZeros(n)) + + /** Remove keys from this map and set back to initial configuration */ + def clear(): Unit = + used = 0 + allocate(roundToPower(initialCapacity max 4)) + + /** The number of keys */ + final def size: Int = used + + /** The number of keys that can be stored without growing the tables */ + final def capacity: Int = keys.length + + private final def isDense = capacity <= DenseLimit + + /** Hashcode, by default a post-processed versoon of `k.hashCode`, + * can be overridden + */ + protected def hash(k: Key): Int = + val h = k.hashCode + // Part of the MurmurHash3 32 bit finalizer + val i = (h ^ (h >>> 16)) * 0x85EBCA6B + val j = (i ^ (i >>> 13)) & 0x7FFFFFFF + if (j==0) 0x41081989 else j + + /** Equality test, by default `equals`, can be overridden */ + protected def isEqual(x: Key, y: Key): Boolean = x.equals(y) + + private def matches(entry: Int, k: Key) = isEqual(key(entry), k) + + private def tableIndex(x: Int): Int = x & (table.length - 1) + private def firstIndex(k: Key) = tableIndex(hash(k)) + private def nextIndex(idx: Int) = tableIndex(idx + 1) + + /** The key at index `idx` */ + def key(idx: Int) = keys(idx).asInstanceOf[Key] + private def setKey(e: Int, k: Key) = keys(e) = k.asInstanceOf[AnyRef] + + private def entry(idx: Int): Int = table(idx) - 1 + private def setEntry(idx: Int, entry: Int) = table(idx) = entry + 1 + + /** An index `idx` such that `key(idx) == k`, or -1 if no such index exists */ + def index(k: Key): Int = + if isDense then + var e = 0 + while e < used do + if matches(e, k) then return e + e += 1 + -1 + else + var idx = firstIndex(k) + var e = entry(idx) + while e >= 0 && !matches(e, k) do + idx = nextIndex(idx) + e = entry(idx) + e + + /** An index `idx` such that key(idx) == k. + * If no such index exists, create an entry with an index one + * larger than the previous one. + */ + def add(k: Key): Int = + if isDense then + var e = 0 + while e < used do + if matches(e, k) then return e + e += 1 + else + var idx = firstIndex(k) + var e = entry(idx) + while e >= 0 do + if matches(e, k) then return e + idx = nextIndex(idx) + e = entry(idx) + setEntry(idx, used) + end if + setKey(used, k) + used = used + 1 + if used == capacity then growTable() + used - 1 + + private def rehash(): Unit = + var e = 0 + while e < used do + var idx = firstIndex(key(e)) + while entry(idx) >= 0 do idx = nextIndex(idx) + setEntry(idx, e) + e += 1 + + /** Grow backing arrays */ + protected def growTable(): Unit = + val oldKeys = keys + allocate(capacity * 2) + Array.copy(oldKeys, 0, keys, 0, oldKeys.length) + if !isDense then rehash() + + def keysIterator: Iterator[Key] = + keys.iterator.take(used).asInstanceOf[Iterator[Key]] +end PerfectHashing diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index ba73ffd67167..0c1e022d9574 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -181,49 +181,16 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends } override def toString: String = file.toString - - // Positioned ids - - private val ctr = new AtomicInteger - - def nextId: Int = { - val id = ctr.get - if (id % ChunkSize == 0) newChunk - else if (ctr.compareAndSet(id, id + 1)) id - else nextId - } - - private def newChunk: Int = sourceOfChunk.synchronized { - val id = chunks << ChunkSizeLog - if (chunks == sourceOfChunk.length) { - val a = new Array[SourceFile](chunks * 2) - System.arraycopy(sourceOfChunk, 0, a, 0, chunks) - sourceOfChunk = a - } - sourceOfChunk(chunks) = this - chunks += 1 - ctr.set(id + 1) - id - } } object SourceFile { implicit def eqSource: Eql[SourceFile, SourceFile] = Eql.derived implicit def fromContext(using Context): SourceFile = ctx.source - def fromId(id: Int): SourceFile = sourceOfChunk(id >> ChunkSizeLog) - def virtual(name: String, content: String, maybeIncomplete: Boolean = false) = val src = new SourceFile(new VirtualFile(name, content.getBytes(StandardCharsets.UTF_8)), scala.io.Codec.UTF8) src._maybeInComplete = maybeIncomplete src - - private final val ChunkSizeLog = 10 - private final val ChunkSize = 1 << ChunkSizeLog - - // These two vars are sharable because they're only used in the synchronized block in newChunk - @sharable private var chunks: Int = 0 - @sharable private var sourceOfChunk: Array[SourceFile] = new Array[SourceFile](2000) } @sharable object NoSource extends SourceFile(NoAbstractFile, Array[Char]()) { diff --git a/compiler/src/dotty/tools/dotc/util/SparseIntArray.scala b/compiler/src/dotty/tools/dotc/util/SparseIntArray.scala deleted file mode 100644 index 52c77717845a..000000000000 --- a/compiler/src/dotty/tools/dotc/util/SparseIntArray.scala +++ /dev/null @@ -1,239 +0,0 @@ -package dotty.tools.dotc -package util - -import java.util.NoSuchElementException - -class SparseIntArray: - import SparseIntArray._ - - private var siz: Int = 0 - private var root: Node = LeafNode() - - private def grow() = - val newRoot = InnerNode(root.level + 1) - newRoot.elems(0) = root - root = newRoot - - private def capacity: Int = root.elemSize * NodeSize - - def size = siz - - def contains(index: Int): Boolean = - 0 <= index && index < capacity && root.contains(index) - - def apply(index: Int): Value = - require(index >= 0) - if index >= capacity then throw NoSuchElementException() - root.apply(index) - - def update(index: Int, value: Value): Unit = - require(index >= 0) - while capacity <= index do - require(root.level < MaxLevels, "array index too large, maximum is 2^30 - 1") - grow() - if !root.update(index, value) then siz += 1 - - /** Remove element at `index` if it is present - * @return element was present - */ - def remove(index: Int): Boolean = - require(index >= 0) - index < capacity && { - val result = root.remove(index) - if result then siz -= 1 - result - } - - /** All defined indices in an iterator */ - def keysIterator: Iterator[Int] = root.keysIterator(0) - - /** Perform operation for each key/value pair */ - def foreachBinding(op: (Int, Value) => Unit): Unit = - root.foreachBinding(op, 0) - - /** Transform each defined value with transformation `op`. - * The transformation takes the element index and value as parameters. - */ - def transform(op: Transform): Unit = - root.transform(op, 0) - - /** Access to some info about low-level representation */ - def repr: Repr = root - - override def toString = - val b = StringBuilder() ++= "SparseIntArray(" - var first = true - foreachBinding { (idx, elem) => - if first then first = false else b ++= ", " - b ++= s"$idx -> $elem" - } - b ++= ")" - b.toString - -object SparseIntArray: - type Value = Int - - trait Transform: - def apply(key: Int, v: Value): Value - - private inline val NodeSizeLog = 5 - private inline val NodeSize = 1 << NodeSizeLog - private inline val MaxLevels = 5 // max size is 2 ^ ((MaxLevels + 1) * NodeSizeLog) = 2 ^ 30 - - /** The exposed representation. Should be used just for nodeCount and - * low-level toString. - */ - abstract class Repr: - def nodeCount: Int - - private abstract class Node(val level: Int) extends Repr: - private[SparseIntArray] def elemShift = level * NodeSizeLog - private[SparseIntArray] def elemSize = 1 << elemShift - private[SparseIntArray] def elemMask = elemSize - 1 - def contains(index: Int): Boolean - def apply(index: Int): Value - def update(index: Int, value: Value): Boolean - def remove(index: Int): Boolean - def isEmpty: Boolean - def keysIterator(offset: Int): Iterator[Int] - def foreachBinding(op: (Int, Value) => Unit, offset: Int): Unit - def transform(op: Transform, offset: Int): Unit - def nodeCount: Int - end Node - - private class LeafNode extends Node(0): - private val elems = new Array[Value](NodeSize) - private var present: Int = 0 - - def contains(index: Int): Boolean = - (present & (1 << index)) != 0 - - def apply(index: Int) = - if !contains(index) then throw NoSuchElementException() - elems(index) - - def update(index: Int, value: Value): Boolean = - elems(index) = value - val result = contains(index) - present = present | (1 << index) - result - - def remove(index: Int): Boolean = - val result = contains(index) - present = present & ~(1 << index) - result - - def isEmpty = present == 0 - - private def skipUndefined(i: Int): Int = - if i < NodeSize && !contains(i) then skipUndefined(i + 1) else i - - def keysIterator(offset: Int) = new Iterator[Int]: - private var curIdx = skipUndefined(0) - def hasNext = curIdx < NodeSize - def next(): Int = - val result = curIdx + offset - curIdx = skipUndefined(curIdx + 1) - result - - def foreachBinding(op: (Int, Value) => Unit, offset: Int): Unit = - var i = 0 - while i < NodeSize do - if contains(i) then op(offset + i, elems(i)) - i += 1 - - def transform(op: Transform, offset: Int): Unit = - var i = 0 - while i < NodeSize do - if contains(i) then elems(i) = op(offset + i, elems(i)) - i += 1 - - def nodeCount = 1 - - override def toString = - elems - .zipWithIndex - .filter((elem, idx) => contains(idx)) - .map((elem, idx) => s"$idx -> $elem").mkString(s"0#(", ", ", ")") - end LeafNode - - private class InnerNode(level: Int) extends Node(level): - private[SparseIntArray] val elems = new Array[Node](NodeSize) - private var empty: Boolean = true - - def contains(index: Int): Boolean = - val elem = elems(index >>> elemShift) - elem != null && elem.contains(index & elemMask) - - def apply(index: Int): Value = - val elem = elems(index >>> elemShift) - if elem == null then throw NoSuchElementException() - elem.apply(index & elemMask) - - def update(index: Int, value: Value): Boolean = - empty = false - var elem = elems(index >>> elemShift) - if elem == null then - elem = newNode(level - 1) - elems(index >>> elemShift) = elem - elem.update(index & elemMask, value) - - def remove(index: Int): Boolean = - val elem = elems(index >>> elemShift) - if elem == null then false - else - val result = elem.remove(index & elemMask) - if elem.isEmpty then - elems(index >>> elemShift) = null - var i = 0 - while i < NodeSize && elems(i) == null do i += 1 - if i == NodeSize then empty = true - result - - def isEmpty = empty - - private def skipUndefined(i: Int): Int = - if i < NodeSize && elems(i) == null then skipUndefined(i + 1) else i - - // Note: This takes (depth of tree) recursive steps to produce the - // next index. It could be more efficient if we kept all active iterators - // in a path. - def keysIterator(offset: Int) = new Iterator[Value]: - private var curIdx = skipUndefined(0) - private var elemIt = Iterator.empty[Int] - def hasNext = elemIt.hasNext || curIdx < NodeSize - def next(): Value = - if elemIt.hasNext then elemIt.next() - else - elemIt = elems(curIdx).keysIterator(offset + curIdx * elemSize) - curIdx = skipUndefined(curIdx + 1) - elemIt.next() - - def foreachBinding(op: (Int, Value) => Unit, offset: Int): Unit = - var i = 0 - while i < NodeSize do - if elems(i) != null then - elems(i).foreachBinding(op, offset + i * elemSize) - i += 1 - - def transform(op: Transform, offset: Int): Unit = - var i = 0 - while i < NodeSize do - if elems(i) != null then - elems(i).transform(op, offset + i * elemSize) - i += 1 - - def nodeCount = - 1 + elems.filter(_ != null).map(_.nodeCount).sum - - override def toString = - elems - .zipWithIndex - .filter((elem, idx) => elem != null) - .map((elem, idx) => s"$idx -> $elem").mkString(s"$level#(", ", ", ")") - end InnerNode - - private def newNode(level: Int): Node = - if level == 0 then LeafNode() else InnerNode(level) - -end SparseIntArray diff --git a/compiler/test/dotty/tools/dotc/util/SparseIntArrayTests.scala b/compiler/test/dotty/tools/dotc/util/SparseIntArrayTests.scala deleted file mode 100644 index 42cecc846452..000000000000 --- a/compiler/test/dotty/tools/dotc/util/SparseIntArrayTests.scala +++ /dev/null @@ -1,38 +0,0 @@ -package dotty.tools.dotc.util - -import org.junit.Assert._ -import org.junit.Test - -class SparseIntArrayTests: - @Test - def sparseArrayTests: Unit = - val a = SparseIntArray() - assert(a.toString == "SparseIntArray()") - a(1) = 22 - assert(a.toString == "SparseIntArray(1 -> 22)") - a(222) = 33 - assert(a.toString == "SparseIntArray(1 -> 22, 222 -> 33)") - a(55555) = 44 - assert(a.toString == "SparseIntArray(1 -> 22, 222 -> 33, 55555 -> 44)") - assert(a.keysIterator.toList == List(1, 222, 55555)) - assert(a.size == 3, a) - assert(a.contains(1), a) - assert(a.contains(222), a) - assert(a.contains(55555), a) - assert(!a.contains(2)) - assert(!a.contains(20000000)) - a(222) = 44 - assert(a.size == 3) - assert(a(1) == 22) - assert(a(222) == 44) - assert(a(55555) == 44) - assert(a.remove(1)) - assert(a.toString == "SparseIntArray(222 -> 44, 55555 -> 44)") - assert(a(222) == 44, a) - assert(a.remove(55555)) - assert(a(222) == 44, a) - assert(a.size == 1) - assert(!a.contains(1)) - assert(!a.remove(55555)) - assert(a.remove(222)) - assert(a.size == 0) diff --git a/tests/run-with-compiler/intmaptest.scala b/tests/run-with-compiler/intmaptest.scala new file mode 100644 index 000000000000..90154290a310 --- /dev/null +++ b/tests/run-with-compiler/intmaptest.scala @@ -0,0 +1,77 @@ +trait Generator[+T]: + self => + def generate: T + def map[S](f: T => S) = new Generator[S]: + def generate: S = f(self.generate) + def flatMap[S](f: T => Generator[S]) = new Generator[S]: + def generate: S = f(self.generate).generate + +object Generator: + val NumLimit = 300 + val Iterations = 10000 + + given integers as Generator[Int]: + val rand = new java.util.Random + def generate = rand.nextInt() + + given booleans as Generator[Boolean] = + integers.map(x => x > 0) + + def range(end: Int): Generator[Int] = + integers.map(x => (x % end).abs) + + enum Op: + case Lookup, Update, Remove + export Op._ + + given ops as Generator[Op] = + range(10).map { + case 0 | 1 | 2 | 3 => Lookup + case 4 | 5 | 6 | 7 => Update + case 8 | 9 => Remove + } + + val nums: Generator[Integer] = range(NumLimit).map(Integer(_)) + +@main def Test = + import Generator._ + + val map1 = dotty.tools.dotc.util.IntMap[Integer]() + val map2 = scala.collection.mutable.HashMap[Integer, Integer]() + + def toOption(n: Int): Option[Integer] = + if n < 0 then None else Some(n) + + def checkSame() = + assert(map1.size == map2.size) + for (k, v) <- map1.iterator do + assert(map2.get(k) == Some(v), s"difference: $map1 / $map2, k = $k, v1 = $v, get2 = ${map2.get(k)}") + for (k, v) <- map2.iterator do + assert(toOption(map1(k)) == Some(v)) + + def lookupTest(num: Integer) = + //println(s"test lookup $num") + val res1 = toOption(map1(num)) + val res2 = map2.get(num) + assert(res1 == res2) + + def updateTest(num: Integer) = + //println(s"test update $num") + lookupTest(num) + map1(num) = num + map2(num) = num + checkSame() + + def removeTest(num: Integer) = ()/* + //println(s"test remove $num") + map1.remove(num) + map2.remove(num) + checkSame()*/ + + for i <- 0 until Iterations do + //if i % 1000 == 0 then println(map1.size) + val num = nums.generate + Generator.ops.generate match + case Lookup => lookupTest(num) + case Update => updateTest(num) + case Remove => removeTest(num)