Skip to content

Commit 9053964

Browse files
authored
Merge pull request #3978 from dotty-staging/fix-#3965-v3
Fix #3965: Make higher-kinded equality correct and efficient
2 parents 668c499 + 15644d6 commit 9053964

15 files changed

+388
-192
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

+2-4
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
306306

307307
def prefixIsElidable(tp: NamedType)(implicit ctx: Context) = {
308308
val typeIsElidable = tp.prefix match {
309-
case NoPrefix =>
310-
true
311309
case pre: ThisType =>
312310
pre.cls.isStaticOwner ||
313311
tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls
@@ -316,8 +314,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
316314
// eg anonymous TypeMap inside TypeMap.andThen
317315
case pre: TermRef =>
318316
pre.symbol.is(Module) && pre.symbol.isStatic
319-
case _ =>
320-
false
317+
case pre =>
318+
pre `eq` NoPrefix
321319
}
322320
typeIsElidable ||
323321
tp.symbol.is(JavaStatic) ||

compiler/src/dotty/tools/dotc/core/Contexts.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,8 @@ object Contexts {
617617
"uniqueNamedTypes" -> uniqueNamedTypes)
618618

619619
/** A map that associates label and size of all uniques sets */
620-
def uniquesSizes: Map[String, Int] = uniqueSets.mapValues(_.size)
620+
def uniquesSizes: Map[String, (Int, Int, Int)] =
621+
uniqueSets.mapValues(s => (s.size, s.accesses, s.misses))
621622

622623
/** Number of findMember calls on stack */
623624
private[core] var findMemberCount: Int = 0

compiler/src/dotty/tools/dotc/core/Hashable.scala

+38-25
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@ package core
33

44
import Types._
55
import scala.util.hashing.{ MurmurHash3 => hashing }
6+
import annotation.tailrec
67

78
object Hashable {
9+
10+
/** A null terminated list of BindingTypes. We use `null` here for efficiency */
11+
class Binders(val tp: BindingType, val next: Binders)
12+
13+
/** A null terminated list of pairs of BindingTypes. Used for isomorphism tests. */
14+
class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs) {
15+
@tailrec final def matches(t1: Type, t2: Type): Boolean =
16+
(t1 `eq` tp1) && (t2 `eq` tp2) || next != null && next.matches(t1, t2)
17+
}
818

919
/** A hash value indicating that the underlying type is not
1020
* cached in uniques.
@@ -33,26 +43,29 @@ trait Hashable {
3343
protected final def finishHash(hashCode: Int, arity: Int): Int =
3444
avoidSpecialHashes(hashing.finalizeHash(hashCode, arity))
3545

36-
final def identityHash = avoidSpecialHashes(System.identityHashCode(this))
46+
final def typeHash(bs: Binders, tp: Type) =
47+
if (bs == null || tp.stableHash) tp.hash else tp.computeHash(bs)
48+
49+
def identityHash(bs: Binders) = avoidSpecialHashes(System.identityHashCode(this))
3750

38-
protected def finishHash(seed: Int, arity: Int, tp: Type): Int = {
39-
val elemHash = tp.hash
51+
protected def finishHash(bs: Binders, seed: Int, arity: Int, tp: Type): Int = {
52+
val elemHash = typeHash(bs, tp)
4053
if (elemHash == NotCached) return NotCached
4154
finishHash(hashing.mix(seed, elemHash), arity + 1)
4255
}
4356

44-
protected def finishHash(seed: Int, arity: Int, tp1: Type, tp2: Type): Int = {
45-
val elemHash = tp1.hash
57+
protected def finishHash(bs: Binders, seed: Int, arity: Int, tp1: Type, tp2: Type): Int = {
58+
val elemHash = typeHash(bs, tp1)
4659
if (elemHash == NotCached) return NotCached
47-
finishHash(hashing.mix(seed, elemHash), arity + 1, tp2)
60+
finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tp2)
4861
}
4962

50-
protected def finishHash(seed: Int, arity: Int, tps: List[Type]): Int = {
63+
protected def finishHash(bs: Binders, seed: Int, arity: Int, tps: List[Type]): Int = {
5164
var h = seed
5265
var xs = tps
5366
var len = arity
5467
while (xs.nonEmpty) {
55-
val elemHash = xs.head.hash
68+
val elemHash = typeHash(bs, xs.head)
5669
if (elemHash == NotCached) return NotCached
5770
h = hashing.mix(h, elemHash)
5871
xs = xs.tail
@@ -61,33 +74,33 @@ trait Hashable {
6174
finishHash(h, len)
6275
}
6376

64-
protected def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = {
65-
val elemHash = tp.hash
77+
protected def finishHash(bs: Binders, seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = {
78+
val elemHash = typeHash(bs, tp)
6679
if (elemHash == NotCached) return NotCached
67-
finishHash(hashing.mix(seed, elemHash), arity + 1, tps)
80+
finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tps)
6881
}
6982

83+
7084
protected final def doHash(x: Any): Int =
7185
finishHash(hashing.mix(hashSeed, x.hashCode), 1)
7286

73-
protected final def doHash(tp: Type): Int =
74-
finishHash(hashSeed, 0, tp)
75-
76-
protected final def doHash(x1: Any, tp2: Type): Int =
77-
finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2)
87+
protected final def doHash(bs: Binders, tp: Type): Int =
88+
finishHash(bs, hashSeed, 0, tp)
7889

79-
protected final def doHash(tp1: Type, tp2: Type): Int =
80-
finishHash(hashSeed, 0, tp1, tp2)
90+
protected final def doHash(bs: Binders, x1: Any, tp2: Type): Int =
91+
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2)
8192

82-
protected final def doHash(x1: Any, tp2: Type, tp3: Type): Int =
83-
finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3)
93+
protected final def doHash(bs: Binders, tp1: Type, tp2: Type): Int =
94+
finishHash(bs, hashSeed, 0, tp1, tp2)
8495

85-
protected final def doHash(tp1: Type, tps2: List[Type]): Int =
86-
finishHash(hashSeed, 0, tp1, tps2)
96+
protected final def doHash(bs: Binders, x1: Any, tp2: Type, tp3: Type): Int =
97+
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3)
8798

88-
protected final def doHash(x1: Any, tp2: Type, tps3: List[Type]): Int =
89-
finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3)
99+
protected final def doHash(bs: Binders, tp1: Type, tps2: List[Type]): Int =
100+
finishHash(bs, hashSeed, 0, tp1, tps2)
90101

102+
protected final def doHash(bs: Binders, x1: Any, tp2: Type, tps3: List[Type]): Int =
103+
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3)
91104

92105
protected final def doHash(x1: Int, x2: Int): Int =
93106
finishHash(hashing.mix(hashing.mix(hashSeed, x1), x2), 1)
@@ -96,7 +109,7 @@ trait Hashable {
96109
if (elemHash == NotCached) NotCached
97110
else avoidSpecialHashes(elemHash + delta)
98111

99-
private def avoidSpecialHashes(h: Int) =
112+
protected def avoidSpecialHashes(h: Int) =
100113
if (h == NotCached) NotCachedAlt
101114
else if (h == HashUnknown) HashUnknownAlt
102115
else h

compiler/src/dotty/tools/dotc/core/Substituters.scala

+20-20
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ trait Substituters { this: Context =>
1212
case tp: BoundType =>
1313
if (tp.binder eq from) tp.copyBoundType(to.asInstanceOf[tp.BT]) else tp
1414
case tp: NamedType =>
15-
if (tp.currentSymbol.isStatic) tp
15+
if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp
1616
else tp.derivedSelect(subst(tp.prefix, from, to, theMap))
17-
case _: ThisType | NoPrefix =>
17+
case _: ThisType =>
1818
tp
1919
case _ =>
2020
(if (theMap != null) theMap else new SubstBindingMap(from, to))
@@ -26,9 +26,9 @@ trait Substituters { this: Context =>
2626
case tp: NamedType =>
2727
val sym = tp.symbol
2828
if (sym eq from) return to
29-
if (sym.isStatic && !from.isStatic) tp
29+
if (sym.isStatic && !from.isStatic || (tp.prefix `eq` NoPrefix)) tp
3030
else tp.derivedSelect(subst1(tp.prefix, from, to, theMap))
31-
case _: ThisType | _: BoundType | NoPrefix =>
31+
case _: ThisType | _: BoundType =>
3232
tp
3333
case _ =>
3434
(if (theMap != null) theMap else new Subst1Map(from, to))
@@ -42,9 +42,9 @@ trait Substituters { this: Context =>
4242
val sym = tp.symbol
4343
if (sym eq from1) return to1
4444
if (sym eq from2) return to2
45-
if (sym.isStatic && !from1.isStatic && !from2.isStatic) tp
45+
if (sym.isStatic && !from1.isStatic && !from2.isStatic || (tp.prefix `eq` NoPrefix)) tp
4646
else tp.derivedSelect(subst2(tp.prefix, from1, to1, from2, to2, theMap))
47-
case _: ThisType | _: BoundType | NoPrefix =>
47+
case _: ThisType | _: BoundType =>
4848
tp
4949
case _ =>
5050
(if (theMap != null) theMap else new Subst2Map(from1, to1, from2, to2))
@@ -63,9 +63,9 @@ trait Substituters { this: Context =>
6363
fs = fs.tail
6464
ts = ts.tail
6565
}
66-
if (sym.isStatic && !existsStatic(from)) tp
66+
if (sym.isStatic && !existsStatic(from) || (tp.prefix `eq` NoPrefix)) tp
6767
else tp.derivedSelect(subst(tp.prefix, from, to, theMap))
68-
case _: ThisType | _: BoundType | NoPrefix =>
68+
case _: ThisType | _: BoundType =>
6969
tp
7070
case _ =>
7171
(if (theMap != null) theMap else new SubstMap(from, to))
@@ -84,7 +84,7 @@ trait Substituters { this: Context =>
8484
fs = fs.tail
8585
ts = ts.tail
8686
}
87-
if (sym.isStatic && !existsStatic(from)) tp
87+
if (sym.isStatic && !existsStatic(from) || (tp.prefix `eq` NoPrefix)) tp
8888
else {
8989
tp.info match {
9090
case TypeAlias(alias) =>
@@ -94,7 +94,7 @@ trait Substituters { this: Context =>
9494
}
9595
tp.derivedSelect(substDealias(tp.prefix, from, to, theMap))
9696
}
97-
case _: ThisType | _: BoundType | NoPrefix =>
97+
case _: ThisType | _: BoundType =>
9898
tp
9999
case _ =>
100100
(if (theMap != null) theMap else new SubstDealiasMap(from, to))
@@ -114,7 +114,7 @@ trait Substituters { this: Context =>
114114
fs = fs.tail
115115
ts = ts.tail
116116
}
117-
if (sym.isStatic && !existsStatic(from)) tp
117+
if (sym.isStatic && !existsStatic(from) || (tp.prefix `eq` NoPrefix)) tp
118118
else tp.derivedSelect(substSym(tp.prefix, from, to, theMap))
119119
case tp: ThisType =>
120120
val sym = tp.cls
@@ -126,7 +126,7 @@ trait Substituters { this: Context =>
126126
ts = ts.tail
127127
}
128128
tp
129-
case _: ThisType | _: BoundType | NoPrefix =>
129+
case _: ThisType | _: BoundType =>
130130
tp
131131
case _ =>
132132
(if (theMap != null) theMap else new SubstSymMap(from, to))
@@ -138,9 +138,9 @@ trait Substituters { this: Context =>
138138
case tp: ThisType =>
139139
if (tp.cls eq from) to else tp
140140
case tp: NamedType =>
141-
if (tp.currentSymbol.isStaticOwner) tp
141+
if (tp.currentSymbol.isStaticOwner || (tp.prefix `eq` NoPrefix)) tp
142142
else tp.derivedSelect(substThis(tp.prefix, from, to, theMap))
143-
case _: BoundType | NoPrefix =>
143+
case _: BoundType =>
144144
tp
145145
case _ =>
146146
(if (theMap != null) theMap else new SubstThisMap(from, to))
@@ -152,9 +152,9 @@ trait Substituters { this: Context =>
152152
case tp @ RecThis(binder) =>
153153
if (binder eq from) to else tp
154154
case tp: NamedType =>
155-
if (tp.currentSymbol.isStatic) tp
155+
if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp
156156
else tp.derivedSelect(substRecThis(tp.prefix, from, to, theMap))
157-
case _: ThisType | _: BoundType | NoPrefix =>
157+
case _: ThisType | _: BoundType =>
158158
tp
159159
case _ =>
160160
(if (theMap != null) theMap else new SubstRecThisMap(from, to))
@@ -166,9 +166,9 @@ trait Substituters { this: Context =>
166166
case tp: BoundType =>
167167
if (tp == from) to else tp
168168
case tp: NamedType =>
169-
if (tp.currentSymbol.isStatic) tp
169+
if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp
170170
else tp.derivedSelect(substParam(tp.prefix, from, to, theMap))
171-
case _: ThisType | NoPrefix =>
171+
case _: ThisType =>
172172
tp
173173
case _ =>
174174
(if (theMap != null) theMap else new SubstParamMap(from, to))
@@ -180,9 +180,9 @@ trait Substituters { this: Context =>
180180
case tp: ParamRef =>
181181
if (tp.binder == from) to(tp.paramNum) else tp
182182
case tp: NamedType =>
183-
if (tp.currentSymbol.isStatic) tp
183+
if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp
184184
else tp.derivedSelect(substParams(tp.prefix, from, to, theMap))
185-
case _: ThisType | NoPrefix =>
185+
case _: ThisType =>
186186
tp
187187
case _ =>
188188
(if (theMap != null) theMap else new SubstParamsMap(from, to))

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ object SymDenotations {
461461
/** Is symbol known to not exist? */
462462
final def isAbsent(implicit ctx: Context): Boolean = {
463463
ensureCompleted()
464-
myInfo == NoType ||
464+
(myInfo `eq` NoType) ||
465465
(this is (ModuleVal, butNot = Package)) && moduleClass.isAbsent
466466
}
467467

@@ -1296,7 +1296,7 @@ object SymDenotations {
12961296
private[this] var myMemberCachePeriod: Period = Nowhere
12971297

12981298
/** A cache from types T to baseType(T, C) */
1299-
type BaseTypeMap = java.util.HashMap[CachedType, Type]
1299+
type BaseTypeMap = java.util.IdentityHashMap[CachedType, Type]
13001300
private[this] var myBaseTypeCache: BaseTypeMap = null
13011301
private[this] var myBaseTypeCachePeriod: Period = Nowhere
13021302

@@ -1720,7 +1720,7 @@ object SymDenotations {
17201720
btrCache.put(tp, basetp)
17211721
}
17221722
else btrCache.remove(tp)
1723-
} else if (basetp == NoPrefix)
1723+
} else if (basetp `eq` NoPrefix)
17241724
throw CyclicReference(this)
17251725
basetp
17261726
}

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ object TypeErasure {
7878
*/
7979
abstract case class ErasedValueType(tycon: TypeRef, erasedUnderlying: Type)
8080
extends CachedGroundType with ValueType {
81-
override def computeHash = doHash(tycon, erasedUnderlying)
81+
override def computeHash(bs: Hashable.Binders) = doHash(bs, tycon, erasedUnderlying)
8282
}
8383

8484
final class CachedErasedValueType(tycon: TypeRef, erasedUnderlying: Type)
@@ -429,10 +429,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
429429
tp.derivedClassInfo(NoPrefix, erasedParents, erasedDecls, erasedRef(tp.selfType))
430430
// can't replace selftype by NoType because this would lose the sourceModule link
431431
}
432-
case NoType | NoPrefix | _: ErrorType | JavaArrayType(_) =>
432+
case _: ErrorType | JavaArrayType(_) =>
433433
tp
434434
case tp: WildcardType if wildcardOK =>
435435
tp
436+
case tp if (tp `eq` NoType) || (tp `eq` NoPrefix) =>
437+
tp
436438
}
437439

438440
private def eraseArray(tp: Type)(implicit ctx: Context) = {

compiler/src/dotty/tools/dotc/core/TypeOps.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
5757
tp match {
5858
case tp: NamedType =>
5959
val sym = tp.symbol
60-
if (sym.isStatic) tp
60+
if (sym.isStatic || (tp.prefix `eq` NoPrefix)) tp
6161
else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix)))
6262
case tp: ThisType =>
6363
toPrefix(pre, cls, tp.cls)
64-
case _: BoundType | NoPrefix =>
64+
case _: BoundType =>
6565
tp
6666
case _ =>
6767
mapOver(tp)
@@ -80,7 +80,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
8080
/** Implementation of Types#simplified */
8181
final def simplify(tp: Type, theMap: SimplifyMap): Type = tp match {
8282
case tp: NamedType =>
83-
if (tp.symbol.isStatic) tp
83+
if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp
8484
else tp.derivedSelect(simplify(tp.prefix, theMap)) match {
8585
case tp1: NamedType if tp1.denotationIsCurrent =>
8686
val tp2 = tp1.reduceProjection
@@ -97,7 +97,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
9797
val tvar = typerState.constraint.typeVarOfParam(tp)
9898
if (tvar.exists) tvar else tp
9999
}
100-
case _: ThisType | _: BoundType | NoPrefix =>
100+
case _: ThisType | _: BoundType =>
101101
tp
102102
case tp: TypeAlias =>
103103
tp.derivedTypeAlias(simplify(tp.alias, theMap))

0 commit comments

Comments
 (0)