Skip to content

Commit 8a5cb06

Browse files
committed
Drop bounds on HashMap Value type
Make lookup return a Value | Null result instead, which makes it a bit safer to use. To support this well, forward port a casting method from the explicit-nulls branch.
1 parent 712cb06 commit 8a5cb06

File tree

7 files changed

+50
-58
lines changed

7 files changed

+50
-58
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class Compiler {
109109
List(new Constructors, // Collect initialization code in primary constructors
110110
// Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it
111111
new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions.
112-
new Instrumentation) :: // Count closure allocations under -Yinstrument-closures
112+
new Instrumentation) :: // Count calls and allocations under -Yinstrument
113113
List(new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments
114114
// Note: in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here
115115
new ElimStaticThis, // Replace `this` references to static objects by global identifiers

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class TreePickler(pickler: TastyPickler) {
4444

4545
private val symRefs = Symbols.newMutableSymbolMap[Addr]
4646
private val forwardSymRefs = Symbols.newMutableSymbolMap[List[Addr]]
47-
private val pickledTypes = util.IdentityHashMap[Type, AnyRef]() // Value type is really Addr, but that's not compatible with null
47+
private val pickledTypes = util.IdentityHashMap[Type, Addr]()
4848

4949
/** A list of annotation trees for every member definition, so that later
5050
* parallel position pickling does not need to access and force symbols.
@@ -169,14 +169,14 @@ class TreePickler(pickler: TastyPickler) {
169169
def pickleType(tpe0: Type, richTypes: Boolean = false)(using Context): Unit = {
170170
val tpe = tpe0.stripTypeVar
171171
try {
172-
val prev = pickledTypes.lookup(tpe)
172+
val prev: Addr | Null = pickledTypes.lookup(tpe)
173173
if (prev == null) {
174-
pickledTypes(tpe) = currentAddr.asInstanceOf[AnyRef]
174+
pickledTypes(tpe) = currentAddr
175175
pickleNewType(tpe, richTypes)
176176
}
177177
else {
178178
writeByte(SHAREDtype)
179-
writeRef(prev.asInstanceOf[Addr])
179+
writeRef(prev.uncheckedNN)
180180
}
181181
}
182182
catch {
@@ -244,9 +244,9 @@ class TreePickler(pickler: TastyPickler) {
244244
withLength { pickleType(tpe.thistpe); pickleType(tpe.supertpe) }
245245
case tpe: RecThis =>
246246
writeByte(RECthis)
247-
val binderAddr = pickledTypes.lookup(tpe.binder)
247+
val binderAddr: Addr | Null = pickledTypes.lookup(tpe.binder)
248248
assert(binderAddr != null, tpe.binder)
249-
writeRef(binderAddr.asInstanceOf[Addr])
249+
writeRef(binderAddr.uncheckedNN)
250250
case tpe: SkolemType =>
251251
pickleType(tpe.info)
252252
case tpe: RefinedType =>
@@ -314,11 +314,11 @@ class TreePickler(pickler: TastyPickler) {
314314
}
315315

316316
def pickleParamRef(tpe: ParamRef)(using Context): Boolean = {
317-
val binder = pickledTypes.lookup(tpe.binder)
317+
val binder: Addr | Null = pickledTypes.lookup(tpe.binder)
318318
val pickled = binder != null
319319
if (pickled) {
320320
writeByte(PARAMtype)
321-
withLength { writeRef(binder.asInstanceOf[Addr]); writeNat(tpe.paramNum) }
321+
withLength { writeRef(binder.uncheckedNN); writeNat(tpe.paramNum) }
322322
}
323323
pickled
324324
}
@@ -605,7 +605,7 @@ class TreePickler(pickler: TastyPickler) {
605605
else {
606606
val refineCls = refinements.head.symbol.owner.asClass
607607
registerDef(refineCls)
608-
pickledTypes(refineCls.typeRef) = currentAddr.asInstanceOf[AnyRef]
608+
pickledTypes(refineCls.typeRef) = currentAddr
609609
writeByte(REFINEDtpt)
610610
refinements.foreach(preRegister)
611611
withLength { pickleTree(parent); refinements.foreach(pickleTree) }

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc.util
1+
package dotty.tools
2+
package dotc.util
23

34
object GenericHashMap:
45

@@ -22,7 +23,7 @@ object GenericHashMap:
2223
* However, a table of size up to DenseLimit will be re-sized only
2324
* once the number of elements reaches the table's size.
2425
*/
25-
abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
26+
abstract class GenericHashMap[Key <: AnyRef, Value]
2627
(initialCapacity: Int, capacityMultiple: Int) extends MutableMap[Key, Value]:
2728
import GenericHashMap.DenseLimit
2829

@@ -57,17 +58,20 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
5758
protected def isEqual(x: Key, y: Key): Boolean
5859

5960
/** Turn successor index or hash code `x` into a table index */
60-
private def index(x: Int): Int = x & (table.length - 2)
61+
inline protected def index(x: Int): Int = x & (table.length - 2)
6162

62-
private def firstIndex(key: Key) = if isDense then 0 else index(hash(key))
63-
private def nextIndex(idx: Int) =
63+
inline protected def firstIndex(key: Key) = if isDense then 0 else index(hash(key))
64+
inline protected def nextIndex(idx: Int) =
6465
Stats.record(statsItem("miss"))
6566
index(idx + 2)
6667

67-
private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key]
68-
private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
68+
inline protected def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key]
69+
inline protected def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
6970

70-
def lookup(key: Key): Value =
71+
inline protected def setTable(idx: Int, value: Value) =
72+
table(idx) = value.asInstanceOf[AnyRef]
73+
74+
def lookup(key: Key): Value | Null =
7175
Stats.record(statsItem("lookup"))
7276
var idx = firstIndex(key)
7377
var k = keyAt(idx)
@@ -83,12 +87,12 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
8387
var k = keyAt(idx)
8488
while k != null do
8589
if isEqual(k, key) then
86-
table(idx + 1) = value
90+
setTable(idx + 1, value)
8791
return
8892
idx = nextIndex(idx)
8993
k = keyAt(idx)
9094
table(idx) = key
91-
table(idx + 1) = value
95+
setTable(idx + 1, value)
9296
used += 1
9397
if used > limit then growTable()
9498

@@ -109,7 +113,7 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
109113
// hash(k) is then logically at or before hole; can be moved forward to fill hole
110114
then
111115
table(hole) = k
112-
table(hole + 1) = valueAt(idx)
116+
setTable(hole + 1, valueAt(idx))
113117
hole = idx
114118
table(hole) = null
115119
used -= 1
@@ -118,9 +122,9 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
118122
k = keyAt(idx)
119123

120124
def getOrElseUpdate(key: Key, value: => Value): Value =
121-
var v = lookup(key)
125+
var v: Value | Null = lookup(key)
122126
if v == null then v = value
123-
v
127+
v.uncheckedNN
124128

125129
private def addOld(key: Key, value: AnyRef): Unit =
126130
Stats.record(statsItem("re-enter"))

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotty.tools.dotc.util
33
/** A specialized implementation of GenericHashMap with standard hashCode and equals
44
* as comparison
55
*/
6-
class HashMap[Key <: AnyRef, Value >: Null <: AnyRef]
6+
class HashMap[Key <: AnyRef, Value]
77
(initialCapacity: Int = 8, capacityMultiple: Int = 2)
88
extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
99
import GenericHashMap.DenseLimit
@@ -18,18 +18,7 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
1818
// The following methods are duplicated from GenericHashMap
1919
// to avoid polymorphic dispatches
2020

21-
/** Turn successor index or hash code `x` into a table index */
22-
private def index(x: Int): Int = x & (table.length - 2)
23-
24-
private def firstIndex(key: Key) = if isDense then 0 else index(hash(key))
25-
private def nextIndex(idx: Int) =
26-
Stats.record(statsItem("miss"))
27-
index(idx + 2)
28-
29-
private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key]
30-
private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
31-
32-
override def lookup(key: Key): Value =
21+
override def lookup(key: Key): Value | Null =
3322
Stats.record(statsItem("lookup"))
3423
var idx = firstIndex(key)
3524
var k = keyAt(idx)
@@ -45,12 +34,12 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
4534
var k = keyAt(idx)
4635
while k != null do
4736
if isEqual(k, key) then
48-
table(idx + 1) = value
37+
setTable(idx + 1, value)
4938
return
5039
idx = nextIndex(idx)
5140
k = keyAt(idx)
5241
table(idx) = key
53-
table(idx + 1) = value
42+
setTable(idx + 1, value)
5443
used += 1
5544
if used > limit then growTable()
5645

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotty.tools.dotc.util
33
/** A specialized implementation of GenericHashMap with identity hash and `eq`
44
* as comparison.
55
*/
6-
class IdentityHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
6+
class IdentityHashMap[Key <: AnyRef, Value]
77
(initialCapacity: Int = 8, capacityMultiple: Int = 2)
88
extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
99
import GenericHashMap.DenseLimit
@@ -21,18 +21,7 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
2121
// Aside: It would be nice to have a @specialized annotation that does
2222
// this automatically
2323

24-
/** Turn successor index or hash code `x` into a table index */
25-
private def index(x: Int): Int = x & (table.length - 2)
26-
27-
private def firstIndex(key: Key) = if isDense then 0 else index(hash(key))
28-
private def nextIndex(idx: Int) =
29-
Stats.record(statsItem("miss"))
30-
index(idx + 2)
31-
32-
private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key]
33-
private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
34-
35-
override def lookup(key: Key): Value =
24+
override def lookup(key: Key): Value | Null =
3625
Stats.record(statsItem("lookup"))
3726
var idx = firstIndex(key)
3827
var k = keyAt(idx)
@@ -48,12 +37,12 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
4837
var k = keyAt(idx)
4938
while k != null do
5039
if isEqual(k, key) then
51-
table(idx + 1) = value
40+
setTable(idx + 1, value)
5241
return
5342
idx = nextIndex(idx)
5443
k = keyAt(idx)
5544
table(idx) = key
56-
table(idx + 1) = value
45+
setTable(idx + 1, value)
5746
used += 1
5847
if used > limit then growTable()
5948

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
package dotty.tools.dotc.util
1+
package dotty.tools
2+
package dotc.util
23

34
/** A common class for lightweight mutable maps.
45
*/
5-
abstract class MutableMap[Key <: AnyRef, Value >: Null <: AnyRef]:
6+
abstract class MutableMap[Key <: AnyRef, Value]:
67

7-
def lookup(x: Key): Value /* | Null */
8+
def lookup(x: Key): Value | Null
89

910
def update(k: Key, v: Value): Unit
1011

@@ -16,5 +17,6 @@ abstract class MutableMap[Key <: AnyRef, Value >: Null <: AnyRef]:
1617

1718
def iterator: Iterator[(Key, Value)]
1819

19-
def get(x: Key): Option[Value] = Option(lookup(x))
20-
20+
def get(x: Key): Option[Value] = lookup(x) match
21+
case null => None
22+
case v => Some(v.uncheckedNN)

compiler/src/dotty/tools/package.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ package object tools {
2424
def unsupported(methodName: String): Nothing =
2525
throw new UnsupportedOperationException(methodName)
2626

27+
/** Forward-ported from the explicit-nulls branch.
28+
* Should be used when we know from the context that `x` is not null.
29+
* Flow-typing under explicit nulls will automatically insert many necessary
30+
* occurrences of uncheckedNN.
31+
*/
32+
extension [T](x: T | Null)
33+
inline def uncheckedNN: T = x.asInstanceOf[T]
34+
2735
object resultWrapper {
2836
opaque type WrappedResult[T] = T
2937
private[tools] def unwrap[T](x: WrappedResult[T]): T = x

0 commit comments

Comments
 (0)