Skip to content

Commit a813f2f

Browse files
committed
add unit tests for maps and sets
1 parent 04fb40d commit a813f2f

File tree

6 files changed

+498
-62
lines changed

6 files changed

+498
-62
lines changed

compiler/src/dotty/tools/dotc/util/EqHashSet.scala

-30
Original file line numberDiff line numberDiff line change
@@ -85,36 +85,6 @@ class EqHashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends
8585

8686
override def +=(x: T): Unit = put(x)
8787

88-
override def remove(x: T): Boolean =
89-
Stats.record(statsItem("remove"))
90-
var idx = firstIndex(x)
91-
var e: T | Null = entryAt(idx)
92-
while e != null do
93-
if isEqual(e.uncheckedNN, x) then
94-
var hole = idx
95-
while
96-
idx = nextIndex(idx)
97-
e = entryAt(idx)
98-
e != null
99-
do
100-
val eidx = index(hash(e.uncheckedNN))
101-
if isDense
102-
|| index(eidx - (hole + 1)) > index(idx - (hole + 1))
103-
// entry `e` at `idx` can move unless `index(hash(e))` is in
104-
// the (ring-)interval [hole + 1 .. idx]
105-
then
106-
setEntry(hole, e.uncheckedNN)
107-
hole = idx
108-
table(hole) = null
109-
used -= 1
110-
return true
111-
idx = nextIndex(idx)
112-
e = entryAt(idx)
113-
false
114-
115-
override def -=(x: T): Unit =
116-
remove(x)
117-
11888
private def addOld(x: T) =
11989
Stats.record(statsItem("re-enter"))
12090
var idx = firstIndex(x)

compiler/src/dotty/tools/dotc/util/HashSet.scala

+10-32
Original file line numberDiff line numberDiff line change
@@ -64,48 +64,29 @@ class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends Ge
6464
if used > limit then growTable()
6565
x
6666

67-
override def put(x: T): T =
68-
Stats.record(statsItem("put"))
67+
override def add(x: T): Boolean =
68+
Stats.record(statsItem("enter"))
6969
var idx = firstIndex(x)
7070
var e: T | Null = entryAt(idx)
7171
while e != null do
72-
// TODO: remove uncheckedNN when explicit-nulls is enabled for regule compiling
73-
if isEqual(e.uncheckedNN, x) then return e.uncheckedNN
72+
if isEqual(e.uncheckedNN, x) then return false // already entered
7473
idx = nextIndex(idx)
7574
e = entryAt(idx)
7675
addEntryAt(idx, x)
76+
true // first entry
7777

78-
override def +=(x: T): Unit = put(x)
79-
80-
override def remove(x: T): Boolean =
81-
Stats.record(statsItem("remove"))
78+
override def put(x: T): T =
79+
Stats.record(statsItem("put"))
8280
var idx = firstIndex(x)
8381
var e: T | Null = entryAt(idx)
8482
while e != null do
85-
if isEqual(e.uncheckedNN, x) then
86-
var hole = idx
87-
while
88-
idx = nextIndex(idx)
89-
e = entryAt(idx)
90-
e != null
91-
do
92-
val eidx = index(hash(e.uncheckedNN))
93-
if isDense
94-
|| index(eidx - (hole + 1)) > index(idx - (hole + 1))
95-
// entry `e` at `idx` can move unless `index(hash(e))` is in
96-
// the (ring-)interval [hole + 1 .. idx]
97-
then
98-
setEntry(hole, e.uncheckedNN)
99-
hole = idx
100-
table(hole) = null
101-
used -= 1
102-
return true
83+
// TODO: remove uncheckedNN when explicit-nulls is enabled for regule compiling
84+
if isEqual(e.uncheckedNN, x) then return e.uncheckedNN
10385
idx = nextIndex(idx)
10486
e = entryAt(idx)
105-
false
87+
addEntryAt(idx, x)
10688

107-
override def -=(x: T): Unit =
108-
remove(x)
89+
override def +=(x: T): Unit = put(x)
10990

11091
private def addOld(x: T) =
11192
Stats.record(statsItem("re-enter"))
@@ -125,7 +106,4 @@ class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends Ge
125106
val e: T | Null = oldTable(idx).asInstanceOf[T | Null]
126107
if e != null then addOld(e.uncheckedNN)
127108
idx += 1
128-
129-
override def iterator: Iterator[T] = new EntryIterator():
130-
def entry(idx: Int) = entryAt(idx)
131109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package dotty.tools.dotc.util
2+
3+
import org.junit.Test
4+
import org.junit.Assert.*
5+
6+
class EqHashMapTest:
7+
8+
var counter = 0
9+
10+
// basic identity hash, and reference equality, but with a counter for ordering
11+
class Id:
12+
val count = { counter += 1; counter }
13+
14+
val id1, id2, id3 = Id()
15+
16+
given Ordering[Id] = Ordering.by(_.count)
17+
18+
@Test
19+
def invariant: Unit =
20+
assert((id1 ne id2) && (id1 ne id3) && (id2 ne id3))
21+
22+
@Test
23+
def newEmpty: Unit =
24+
val m = EqHashMap[Id, Int]()
25+
assert(m.size == 0)
26+
assert(m.iterator.toList == Nil)
27+
28+
@Test
29+
def update: Unit =
30+
val m = EqHashMap[Id, Int]()
31+
assert(m.size == 0 && !m.contains(id1))
32+
m.update(id1, 1)
33+
assert(m.size == 1 && m(id1) == 1)
34+
m.update(id1, 2) // replace value
35+
assert(m.size == 1 && m(id1) == 2)
36+
m.update(id3, 3) // new key
37+
assert(m.size == 2 && m(id1) == 2 && m(id3) == 3)
38+
39+
@Test
40+
def getOrElseUpdate: Unit =
41+
val m = EqHashMap[Id, Int]()
42+
// add id1
43+
assert(m.size == 0 && !m.contains(id1))
44+
val added = m.getOrElseUpdate(id1, 1)
45+
assert(added == 1 && m.size == 1 && m(id1) == 1)
46+
// try add id1 again
47+
val addedAgain = m.getOrElseUpdate(id1, 23)
48+
assert(addedAgain != 23 && m.size == 1 && m(id1) == 1) // no change
49+
50+
private def fullMap() =
51+
val m = EqHashMap[Id, Int]()
52+
m.update(id1, 1)
53+
m.update(id2, 2)
54+
m
55+
56+
@Test
57+
def remove: Unit =
58+
val m = fullMap()
59+
// remove id2
60+
m.remove(id2)
61+
assert(m.size == 1)
62+
assert(m.contains(id1) && !m.contains(id2))
63+
// remove id1
64+
m -= id1
65+
assert(m.size == 0)
66+
assert(!m.contains(id1) && !m.contains(id2))
67+
68+
@Test
69+
def lookup: Unit =
70+
val m = fullMap()
71+
assert(m.lookup(id1) == 1)
72+
assert(m.lookup(id2) == 2)
73+
assert(m.lookup(id3) == null)
74+
75+
@Test
76+
def iterator: Unit =
77+
val m = fullMap()
78+
assert(m.iterator.toList.sorted == List(id1 -> 1,id2 -> 2))
79+
80+
@Test
81+
def clear: Unit =
82+
locally:
83+
val s1 = fullMap()
84+
s1.clear()
85+
assert(s1.size == 0)
86+
locally:
87+
val s2 = fullMap()
88+
s2.clear(resetToInitial = false)
89+
assert(s2.size == 0)
90+
91+
// basic structural equality and hash code
92+
class I32(val x: Int):
93+
override def hashCode(): Int = x
94+
override def equals(that: Any): Boolean = that match
95+
case that: I32 => this.x == that.x
96+
case _ => false
97+
98+
/** the hash set is based on reference equality, i.e. does not use universal equality */
99+
@Test
100+
def referenceEquality: Unit =
101+
val i1, i2 = I32(1) // different instances
102+
103+
assert(i1.equals(i2)) // structural equality
104+
assert(i1 ne i2) // reference inequality
105+
106+
val m = locally:
107+
val m = EqHashMap[I32, Int]()
108+
m(i1) = 23
109+
m(i2) = 29
110+
m
111+
112+
assert(m.size == 2 && m(i1) == 23 && m(i2) == 29)
113+
assert(m.keysIterator.toSet == Set(i1)) // scala.Set delegates to universal equality
114+
end referenceEquality
115+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package dotty.tools.dotc.util
2+
3+
import org.junit.Test
4+
import org.junit.Assert.*
5+
6+
class EqHashSetTest:
7+
8+
var counter = 0
9+
10+
// basic identity hash, and reference equality, but with a counter for ordering
11+
class Id:
12+
val count = { counter += 1; counter }
13+
14+
val id1, id2, id3 = Id()
15+
16+
given Ordering[Id] = Ordering.by(_.count)
17+
18+
@Test
19+
def invariant: Unit =
20+
assert((id1 ne id2) && (id1 ne id3) && (id2 ne id3))
21+
22+
@Test
23+
def newEmpty: Unit =
24+
val s = EqHashSet[Id]()
25+
assert(s.size == 0)
26+
assert(s.iterator.toList == Nil)
27+
28+
@Test
29+
def put: Unit =
30+
val s = EqHashSet[Id]()
31+
// put id1
32+
assert(s.size == 0 && !s.contains(id1))
33+
s += id1
34+
assert(s.size == 1 && s.contains(id1))
35+
// put id2
36+
assert(!s.contains(id2))
37+
s.put(id2)
38+
assert(s.size == 2 && s.contains(id1) && s.contains(id2))
39+
// put id3
40+
s ++= List(id3)
41+
assert(s.size == 3 && s.contains(id1) && s.contains(id2) && s.contains(id3))
42+
43+
@Test
44+
def add: Unit =
45+
val s = EqHashSet[Id]()
46+
// add id1
47+
assert(s.size == 0 && !s.contains(id1))
48+
val added = s.add(id1)
49+
assert(added && s.size == 1 && s.contains(id1))
50+
// try add id1 again
51+
val addedAgain = s.add(id1)
52+
assert(!addedAgain && s.size == 1 && s.contains(id1)) // no change
53+
54+
@Test
55+
def construct: Unit =
56+
val s = EqHashSet.from(List(id1,id2,id3))
57+
assert(s.size == 3)
58+
assert(s.contains(id1) && s.contains(id2) && s.contains(id3))
59+
60+
@Test
61+
def remove: Unit =
62+
val s = EqHashSet.from(List(id1,id2,id3))
63+
// remove id2
64+
s.remove(id2)
65+
assert(s.size == 2)
66+
assert(s.contains(id1) && !s.contains(id2) && s.contains(id3))
67+
// remove id1
68+
s -= id1
69+
assert(s.size == 1)
70+
assert(!s.contains(id1) && !s.contains(id2) && s.contains(id3))
71+
// remove id3
72+
s --= List(id3)
73+
assert(s.size == 0)
74+
assert(!s.contains(id1) && !s.contains(id2) && !s.contains(id3))
75+
76+
@Test
77+
def lookup: Unit =
78+
val s = EqHashSet.from(List(id1, id2))
79+
assert(s.lookup(id1) eq id1)
80+
assert(s.lookup(id2) eq id2)
81+
assert(s.lookup(id3) eq null)
82+
83+
@Test
84+
def iterator: Unit =
85+
val s = EqHashSet.from(List(id1,id2,id3))
86+
assert(s.iterator.toList.sorted == List(id1,id2,id3))
87+
88+
@Test
89+
def clear: Unit =
90+
locally:
91+
val s1 = EqHashSet.from(List(id1,id2,id3))
92+
s1.clear()
93+
assert(s1.size == 0)
94+
locally:
95+
val s2 = EqHashSet.from(List(id1,id2,id3))
96+
s2.clear(resetToInitial = false)
97+
assert(s2.size == 0)
98+
99+
// basic structural equality and hash code
100+
class I32(val x: Int):
101+
override def hashCode(): Int = x
102+
override def equals(that: Any): Boolean = that match
103+
case that: I32 => this.x == that.x
104+
case _ => false
105+
106+
/** the hash map is based on reference equality, i.e. does not use universal equality */
107+
@Test
108+
def referenceEquality: Unit =
109+
val i1, i2 = I32(1) // different instances
110+
111+
assert(i1.equals(i2)) // structural equality
112+
assert(i1 ne i2) // reference inequality
113+
114+
val s = EqHashSet.from(List(i1,i2))
115+
116+
assert(s.size == 2 && s.contains(i1) && s.contains(i2))
117+
assert(s.iterator.toSet == Set(i1)) // scala.Set delegates to universal equality
118+
end referenceEquality
119+

0 commit comments

Comments
 (0)