Skip to content

Commit dc7b25c

Browse files
committed
Fix hashing and equality for dependent types
Rework hashing and equality so that two isomorphic types are identified even if they are dependent (i.e. have back edges from a BoundType such as ParamRef or RecThis to its HKLambda or RecType Method and PolyTypes are still generative. This fixes scala#3965
1 parent 232f65a commit dc7b25c

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ package core
33

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

78
object Hashable {
89

9-
class Binders(tp: BindingType, next: Binders)
10-
class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs)
10+
class Binders(tp: BindingType, next: Binders) {
11+
val hash: Int = if (next == null) 31 else next.hash * 41 + 31
12+
}
13+
class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs) {
14+
@tailrec final def matches(t1: Type, t2: Type): Boolean =
15+
(t1 `eq` tp1) && (t2 `eq` tp2) || next != null && next.matches(t1, t2)
16+
}
1117

1218
/** A hash value indicating that the underlying type is not
1319
* cached in uniques.
@@ -95,7 +101,7 @@ trait Hashable {
95101
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3)
96102

97103

98-
protected final def doHash(bs: Binders, x1: Int, x2: Int): Int =
104+
protected final def doHash(x1: Int, x2: Int): Int =
99105
finishHash(hashing.mix(hashing.mix(hashSeed, x1), x2), 1)
100106

101107
protected final def addDelta(elemHash: Int, delta: Int) =

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,10 +1526,10 @@ object Types {
15261526
trait BindingType extends Type {
15271527

15281528
override def identityHash(bs: Binders) =
1529-
if (bs == null) super.identityHash(bs) else ???
1529+
if (bs == null) super.identityHash(bs) else bs.hash
15301530

15311531
def equalBinder(that: BindingType, bs: BinderPairs): Boolean =
1532-
(this `eq` that) /* || ??? */
1532+
(this `eq` that) || bs != null && bs.matches(this, that)
15331533
}
15341534

15351535
/** A trait for proto-types, used as expected types in typer */
@@ -2360,7 +2360,7 @@ object Types {
23602360
refacc.apply(false, tp)
23612361
}
23622362

2363-
override def computeHash(bs: Binders) = doHash(bs, parent)
2363+
override def computeHash(bs: Binders) = doHash(new Binders(this, bs), parent)
23642364

23652365
override def eql(that: Type) = that match {
23662366
case that: RecType => parent.eq(that.parent)
@@ -2369,7 +2369,7 @@ object Types {
23692369

23702370
override def iso(that: Any, bs: BinderPairs) = that match {
23712371
case that: RecType =>
2372-
parent.equals(that.parent, bs/*!!!*/)
2372+
parent.equals(that.parent, new BinderPairs(this, that, bs))
23732373
case _ => false
23742374
}
23752375

@@ -2693,7 +2693,8 @@ object Types {
26932693
abstract class HKLambda extends CachedProxyType with LambdaType {
26942694
final override def underlying(implicit ctx: Context) = resType
26952695

2696-
final override def computeHash(bs: Binders) = doHash(bs, paramNames, resType, paramInfos)
2696+
final override def computeHash(bs: Binders) =
2697+
doHash(new Binders(this, bs), paramNames, resType, paramInfos)
26972698

26982699
final override def eql(that: Type) = that match {
26992700
case that: HKLambda =>
@@ -2709,7 +2710,7 @@ object Types {
27092710
case that: HKLambda =>
27102711
paramNames.eqElements(that.paramNames) &&
27112712
companion.eq(that.companion) && {
2712-
val bs1 = bs
2713+
val bs1 = new BinderPairs(this, that, bs)
27132714
paramInfos.equalElements(that.paramInfos, bs1) &&
27142715
resType.equals(that.resType, bs1)
27152716
}
@@ -3210,7 +3211,7 @@ object Types {
32103211
else infos(paramNum)
32113212
}
32123213

3213-
override def computeHash(bs: Binders) = doHash(bs, paramNum, binder.identityHash(bs))
3214+
override def computeHash(bs: Binders) = doHash(paramNum, binder.identityHash(bs))
32143215

32153216
override def iso(that: Any, bs: BinderPairs) = that match {
32163217
case that: ParamRef => binder.equalBinder(that.binder, bs) && paramNum == that.paramNum

tests/pos/i3965.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Iterable[+A] extends IterableOps[A, Iterable, Iterable[A]]
2+
trait IterableOps[+A, +CCop[_], +C]
3+
4+
trait SortedSet[A] extends Iterable[A] with SortedSetOps[A, SortedSet, SortedSet[A]]
5+
6+
trait SortedSetOps[A, +CCss[X] <: SortedSet[X], +C <: SortedSetOps[A, CCss, C]]
7+
8+
class TreeSet[A]
9+
extends SortedSet[A]
10+
with SortedSetOps[A, TreeSet, TreeSet[A]]
11+
12+
class Test {
13+
def optionSequence1[CCos[X] <: IterableOps[X, CCos, _], A](xs: CCos[Option[A]]): Option[CCos[A]] = ???
14+
def optionSequence1[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], A : Ordering](xs: CC[Option[A]]): Option[CC[A]] = ???
15+
16+
def test(xs2: TreeSet[Option[String]]) = {
17+
optionSequence1(xs2)
18+
}
19+
}

tests/pos/i3965a.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
trait SortedSet[A] extends SortedSetOps[A, SortedSet, SortedSet[A]]
2+
3+
trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]]
4+
5+
class TreeSet[A]
6+
extends SortedSet[A]
7+
with SortedSetOps[A, TreeSet, TreeSet[A]]
8+
9+
class Test {
10+
def optionSequence1[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], A : Ordering](xs: CC[A]): Unit = ()
11+
12+
def test(xs2: TreeSet[String]) = {
13+
optionSequence1(xs2)
14+
}
15+
}

0 commit comments

Comments
 (0)