Skip to content

Fix #3965: Correct equality for higher-kinded types #3970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dotty.tools.dotc.config
object Printers {

class Printer {
val verbose = false
def println(msg: => String): Unit = System.out.println(msg)
}

Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ trait ConstraintHandling {
if (Config.failOnInstantiationToNothing) assert(false, msg)
else ctx.log(msg)
}
constr.println(i"adding $description in ${ctx.typerState.hashesStr}")
constr.println(i"adding $description${ctx.typerState.hashesStr(constr)}")
val lower = constraint.lower(param)
val res =
addOneBound(param, bound, isUpper = true) &&
lower.forall(addOneBound(_, bound, isUpper = true))
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
constr.println(i"added $description = $res${ctx.typerState.hashesStr(constr)}")
res
}

Expand All @@ -108,7 +108,7 @@ trait ConstraintHandling {
val res =
addOneBound(param, bound, isUpper = false) &&
upper.forall(addOneBound(_, bound, isUpper = false))
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
constr.println(i"added $description = $res${ctx.typerState.hashesStr(constr)}")
res
}

Expand All @@ -121,12 +121,12 @@ trait ConstraintHandling {
val up2 = p2 :: constraint.exclusiveUpper(p2, p1)
val lo1 = constraint.nonParamBounds(p1).lo
val hi2 = constraint.nonParamBounds(p2).hi
constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}")
constr.println(i"adding $description down1 = $down1, up2 = $up2${ctx.typerState.hashesStr(constr)}")
constraint = constraint.addLess(p1, p2)
down1.forall(addOneBound(_, hi2, isUpper = true)) &&
up2.forall(addOneBound(_, lo1, isUpper = false))
}
constr.println(i"added $description = $res ${ctx.typerState.hashesStr}")
constr.println(i"added $description = $res${ctx.typerState.hashesStr(constr)}")
res
}

Expand Down
103 changes: 0 additions & 103 deletions compiler/src/dotty/tools/dotc/core/Hashable.scala

This file was deleted.

128 changes: 128 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Hashing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package dotty.tools.dotc
package core

import Types._
import scala.util.hashing.{ MurmurHash3 => hashing }

class Hashing(binders: Array[BindingType]) {
import Hashing._

private def avoidSpecialHashes(h: Int) =
if (h == NotCached) NotCachedAlt
else if (h == HashUnknown) HashUnknownAlt
else h

private def finishHash(hashCode: Int, arity: Int): Int =
avoidSpecialHashes(hashing.finalizeHash(hashCode, arity))

private def typeHash(tp: Type) =
if (binders == null) tp.hash else tp.computeHash(this)

def identityHash(tp: Type): Int = {
if (binders != null) {
var idx = 0
while (idx < binders.length) {
if (binders(idx) `eq` tp) return avoidSpecialHashes(idx * 31)
idx += 1
}
}
avoidSpecialHashes(System.identityHashCode(tp))
}

private def finishHash(seed: Int, arity: Int, tp: Type): Int = {
val elemHash = typeHash(tp)
if (elemHash == NotCached) return NotCached
finishHash(hashing.mix(seed, elemHash), arity + 1)
}

private def finishHash(seed: Int, arity: Int, tp1: Type, tp2: Type): Int = {
val elemHash = typeHash(tp1)
if (elemHash == NotCached) return NotCached
finishHash(hashing.mix(seed, elemHash), arity + 1, tp2)
}

private def finishHash(seed: Int, arity: Int, tps: List[Type]): Int = {
var h = seed
var xs = tps
var len = arity
while (xs.nonEmpty) {
val elemHash = typeHash(xs.head)
if (elemHash == NotCached) return NotCached
h = hashing.mix(h, elemHash)
xs = xs.tail
len += 1
}
finishHash(h, len)
}

private def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = {
val elemHash = typeHash(tp)
if (elemHash == NotCached) return NotCached
finishHash(hashing.mix(seed, elemHash), arity + 1, tps)
}

final def doHash(clazz: Class[_], x: Any): Int =
finishHash(hashing.mix(clazz.hashCode, x.hashCode), 1)

final def doHash(clazz: Class[_], tp: Type): Int =
finishHash(clazz.hashCode, 0, tp)

final def doHash(clazz: Class[_], x1: Any, tp2: Type): Int =
finishHash(hashing.mix(clazz.hashCode, x1.hashCode), 1, tp2)

final def doHash(clazz: Class[_], tp1: Type, tp2: Type): Int =
finishHash(clazz.hashCode, 0, tp1, tp2)

final def doHash(clazz: Class[_], x1: Any, tp2: Type, tp3: Type): Int =
finishHash(hashing.mix(clazz.hashCode, x1.hashCode), 1, tp2, tp3)

final def doHash(clazz: Class[_], tp1: Type, tps2: List[Type]): Int =
finishHash(clazz.hashCode, 0, tp1, tps2)

final def doHash(clazz: Class[_], x1: Any, tp2: Type, tps3: List[Type]): Int =
finishHash(hashing.mix(clazz.hashCode, x1.hashCode), 1, tp2, tps3)

final def doHash(clazz: Class[_], x1: Int, x2: Int): Int =
finishHash(hashing.mix(hashing.mix(clazz.hashCode, x1), x2), 1)

final def addDelta(elemHash: Int, delta: Int) =
if (elemHash == NotCached) NotCached
else avoidSpecialHashes(elemHash + delta)

final def withBinder(binder: BindingType): Hashing = {
new Hashing(
if (binders == null) {
val bs = new Array[BindingType](1)
bs(0) = binder
bs
}
else {
val bs = new Array[BindingType](binders.length + 1)
Array.copy(binders, 0, bs, 0, binders.length)
bs(binders.length) = binder
bs
})
}
}

object Hashing extends Hashing(null) {

/** A hash value indicating that the underlying type is not
* cached in uniques.
*/
final val NotCached = 0

/** An alternative value returned from `hash` if the
* computed hashCode would be `NotCached`.
*/
private[core] final val NotCachedAlt = Int.MinValue

/** A value that indicates that the hash code is unknown
*/
private[core] final val HashUnknown = 1234

/** An alternative value if computeHash would otherwise yield HashUnknown
*/
private[core] final val HashUnknownAlt = 4321
}

36 changes: 36 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StructEquality.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dotty.tools.dotc
package core

import annotation.tailrec
import Types._

class StructEquality(binders1: Array[BindingType], binders2: Array[BindingType]) {

def equals(tp1: Type, tp2: Any): Boolean = (tp1 `eq` tp2.asInstanceOf[AnyRef]) || tp1.iso(tp2, this)

@tailrec final def equals(tps1: List[Type], tps2: List[Type]): Boolean =
(tps1 `eq` tps2) || {
if (tps1.isEmpty) tps2.isEmpty
else tps2.nonEmpty && equals(tps1.head, tps2.head) && equals(tps1.tail, tps2.tail)
}

final def equalBinders(tp1: BindingType, tp2: BindingType): Boolean =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there are two calls in Types.

(tp1 `eq` tp2) || {
var idx = 0
while (idx < binders1.length && (tp1 `ne` binders1(idx)))
idx += 1
idx < binders2.length && (tp2 `eq` binders2(idx))
}

final def withBinders(binder1: BindingType, binder2: BindingType): StructEquality = {
val newBinders1 = new Array[BindingType](binders1.length + 1)
val newBinders2 = new Array[BindingType](binders2.length + 1)
Array.copy(binders1, 0, newBinders1, 0, binders1.length)
Array.copy(binders2, 0, newBinders2, 0, binders2.length)
newBinders1(binders1.length) = binder1
newBinders2(binders1.length) = binder2
new StructEquality(newBinders1, newBinders2)
}
}

object StructEquality extends StructEquality(Array(), Array())
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ object TypeErasure {
*/
abstract case class ErasedValueType(tycon: TypeRef, erasedUnderlying: Type)
extends CachedGroundType with ValueType {
override def computeHash = doHash(tycon, erasedUnderlying)
override def computeHash(h: Hashing) = h.doHash(getClass, tycon, erasedUnderlying)
}

final class CachedErasedValueType(tycon: TypeRef, erasedUnderlying: Type)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab

override def toText(printer: Printer): Text = constraint.toText(printer)

def hashesStr: String =
if (previous == null) "" else hashCode.toString + " -> " + previous.hashesStr
def hashesStr(p: config.Printers.Printer): String =
if (!p.verbose || previous == null) "" else " " + hashCode.toString + " ->" + previous.hashesStr(p)
}
Loading