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 7 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.

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

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

abstract class Hashing {
import Hashing._

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

protected def typeHash(tp: Type) = tp.hash

def identityHash(tp: Type) = avoidSpecialHashes(System.identityHashCode(tp))

protected 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)
}

protected 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)
}

protected 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)
}

protected 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)

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

val binders: Array[BindingType] = Array()

private class WithBinders(override val binders: Array[BindingType]) extends Hashing {

override def typeHash(tp: Type) = tp.computeHash(this)

override def identityHash(tp: Type) = {
var idx = 0
while (idx < binders.length && (binders(idx) `ne` tp))
idx += 1
avoidSpecialHashes(
if (idx < binders.length) idx * 31
else System.identityHashCode(binders))
}
}

def withBinder(binder: BindingType): Hashing = {
val newBinders = new Array[BindingType](binders.length + 1)
Array.copy(binders, 0, newBinders, 0, binders.length)
newBinders(binders.length) = binder
new WithBinders(newBinders)
}
}

object Hashing extends Hashing {

/** 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