diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index d914d4b6d885..1c24884dea8f 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 2fda43a675a9..af5deb670695 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -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 } @@ -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 } @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala deleted file mode 100644 index e4510c53e6c1..000000000000 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ /dev/null @@ -1,103 +0,0 @@ -package dotty.tools.dotc -package core - -import Types._ -import scala.util.hashing.{ MurmurHash3 => hashing } - -object Hashable { - - /** 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 -} - -trait Hashable { - import Hashable._ - - protected def hashSeed: Int = getClass.hashCode - - protected final def finishHash(hashCode: Int, arity: Int): Int = - avoidSpecialHashes(hashing.finalizeHash(hashCode, arity)) - - final def identityHash = avoidSpecialHashes(System.identityHashCode(this)) - - protected def finishHash(seed: Int, arity: Int, tp: Type): Int = { - val elemHash = tp.hash - 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 = tp1.hash - 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 = xs.head.hash - 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 = tp.hash - if (elemHash == NotCached) return NotCached - finishHash(hashing.mix(seed, elemHash), arity + 1, tps) - } - - protected final def doHash(x: Any): Int = - finishHash(hashing.mix(hashSeed, x.hashCode), 1) - - protected final def doHash(tp: Type): Int = - finishHash(hashSeed, 0, tp) - - protected final def doHash(x1: Any, tp2: Type): Int = - finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2) - - protected final def doHash(tp1: Type, tp2: Type): Int = - finishHash(hashSeed, 0, tp1, tp2) - - protected final def doHash(x1: Any, tp2: Type, tp3: Type): Int = - finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3) - - protected final def doHash(tp1: Type, tps2: List[Type]): Int = - finishHash(hashSeed, 0, tp1, tps2) - - protected final def doHash(x1: Any, tp2: Type, tps3: List[Type]): Int = - finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3) - - - protected final def doHash(x1: Int, x2: Int): Int = - finishHash(hashing.mix(hashing.mix(hashSeed, x1), x2), 1) - - protected 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 -} diff --git a/compiler/src/dotty/tools/dotc/core/Hashing.scala b/compiler/src/dotty/tools/dotc/core/Hashing.scala new file mode 100644 index 000000000000..bab8fc0cc8f3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/Hashing.scala @@ -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 +} + diff --git a/compiler/src/dotty/tools/dotc/core/StructEquality.scala b/compiler/src/dotty/tools/dotc/core/StructEquality.scala new file mode 100644 index 000000000000..7b04720bde38 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/StructEquality.scala @@ -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 = + (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()) \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 05dc572dc51e..31ed27e60c05 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 1821735522f5..6ee31c0969f9 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e7f3acf4d033..fd7fa6dc64d3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -27,7 +27,6 @@ import printing.Texts._ import ast.untpd import dotty.tools.dotc.transform.Erasure import printing.Printer -import Hashable._ import Uniques._ import collection.{mutable, Seq, breakOut} import config.Config @@ -84,7 +83,7 @@ object Types { * * Note: please keep in sync with copy in `docs/docs/internals/type-system.md`. */ - abstract class Type extends DotClass with Hashable with printing.Showable { + abstract class Type extends DotClass with printing.Showable { // ----- Tests ----------------------------------------------------- @@ -1394,15 +1393,25 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) + final override def equals(that: Any) = StructEquality.equals(this, that) + + /** Is `this` isomorphic to that, using comparer `e`? + * It is assumed that `this ne that`. + */ + def iso(that: Any, e: StructEquality): Boolean = false + + /** Equality used for hash-consing; uses `eq` on all recursive invocations. + */ + def eql(that: Type): Boolean = this.equals(that) + /** customized hash code of this type. * NotCached for uncached types. Cached types * compute hash and use it as the type's hashCode. */ def hash: Int - /** Equality used for hash-consing; uses `eq` on all recursive invocations. - */ - def eql(that: Type): Boolean = this.equals(that) + /** Compute `hash` using given `Hashing` */ + def computeHash(h: Hashing): Int } // end Type @@ -1437,50 +1446,50 @@ object Types { /** Instances of this class are cached and are not proxies. */ abstract class CachedGroundType extends Type with CachedType { - private[this] var myHash = HashUnknown + private[this] var myHash = Hashing.HashUnknown final def hash = { - if (myHash == HashUnknown) { - myHash = computeHash - assert(myHash != HashUnknown) + if (myHash == Hashing.HashUnknown) { + myHash = computeHash(Hashing) + assert(myHash != Hashing.HashUnknown) } myHash } override final def hashCode = - if (hash == NotCached) System.identityHashCode(this) else hash - def computeHash: Int + if (hash == Hashing.NotCached) System.identityHashCode(this) else hash } /** Instances of this class are cached and are proxies. */ abstract class CachedProxyType extends TypeProxy with CachedType { - protected[this] var myHash = HashUnknown + protected[this] var myHash = Hashing.HashUnknown final def hash = { - if (myHash == HashUnknown) { - myHash = computeHash - assert(myHash != HashUnknown) + if (myHash == Hashing.HashUnknown) { + myHash = computeHash(Hashing) + assert(myHash != Hashing.HashUnknown) } myHash } override final def hashCode = - if (hash == NotCached) System.identityHashCode(this) else hash - def computeHash: Int + if (hash == Hashing.NotCached) System.identityHashCode(this) else hash } /** Instances of this class are uncached and are not proxies. */ abstract class UncachedGroundType extends Type { - final def hash = NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") } + final def hash = Hashing.NotCached + final def computeHash(h: Hashing): Int = Hashing.NotCached } /** Instances of this class are uncached and are proxies. */ abstract class UncachedProxyType extends TypeProxy { - final def hash = NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") } + final def hash = Hashing.NotCached + final def computeHash(h: Hashing): Int = Hashing.NotCached } /** A marker trait for types that apply only to type symbols */ @@ -2008,15 +2017,15 @@ object Types { } } - override def equals(that: Any) = that match { + override def iso(that: Any, e: StructEquality): Boolean = that match { case that: NamedType => this.designator == that.designator && - this.prefix == that.prefix + e.equals(this.prefix, that.prefix) case _ => false } - override def computeHash = unsupported("computeHash") + override def computeHash(h: Hashing) = h.doHash(getClass, designator, prefix) override def eql(that: Type) = this eq that // safe because named types are hash-consed separately } @@ -2140,12 +2149,17 @@ object Types { // can happen in IDE if `cls` is stale } - override def computeHash = doHash(tref) + override def computeHash(h: Hashing) = h.doHash(getClass, tref) override def eql(that: Type) = that match { case that: ThisType => tref.eq(that.tref) case _ => false } + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: ThisType => e.equals(tref, that.tref) + case _ => false + } } final class CachedThisType(tref: TypeRef) extends ThisType(tref) @@ -2168,12 +2182,18 @@ object Types { if ((thistpe eq this.thistpe) && (supertpe eq this.supertpe)) this else SuperType(thistpe, supertpe) - override def computeHash = doHash(thistpe, supertpe) + override def computeHash(h: Hashing) = h.doHash(getClass, thistpe, supertpe) override def eql(that: Type) = that match { case that: SuperType => thistpe.eq(that.thistpe) && supertpe.eq(that.supertpe) case _ => false } + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: SuperType => + e.equals(thistpe, that.thistpe) && e.equals(supertpe, that.supertpe) + case _ => false + } } final class CachedSuperType(thistpe: Type, supertpe: Type) extends SuperType(thistpe, supertpe) @@ -2189,7 +2209,12 @@ object Types { abstract case class ConstantType(value: Constant) extends CachedProxyType with SingletonType { override def underlying(implicit ctx: Context) = value.tpe - override def computeHash = doHash(value) + override def computeHash(h: Hashing) = h.doHash(getClass, value) + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: ConstantType => value.equals(that.value) + case _ => false + } } final class CachedConstantType(value: Constant) extends ConstantType(value) @@ -2216,7 +2241,6 @@ object Types { def evaluating = computed && myRef == null override def underlying(implicit ctx: Context) = ref override def toString = s"LazyRef(${if (computed) myRef else "..."})" - override def equals(other: Any) = this.eq(other.asInstanceOf[AnyRef]) override def hashCode = System.identityHashCode(this) } @@ -2253,7 +2277,7 @@ object Types { if (parent.member(refinedName).exists) derivedRefinedType(parent, refinedName, refinedInfo) else parent - override def computeHash = doHash(refinedName, refinedInfo, parent) + override def computeHash(h: Hashing) = h.doHash(getClass, refinedName, refinedInfo, parent) override def eql(that: Type) = that match { case that: RefinedType => @@ -2262,6 +2286,14 @@ object Types { parent.eq(that.parent) case _ => false } + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: RefinedType => + refinedName.eq(that.refinedName) && + e.equals(refinedInfo, that.refinedInfo) && + e.equals(parent, that.parent) + case _ => false + } } class CachedRefinedType(parent: Type, refinedName: Name, refinedInfo: Type) @@ -2317,15 +2349,11 @@ object Types { refacc.apply(false, tp) } - override def computeHash = doHash(parent) - - override def equals(that: Any) = that match { - case that: RecType => parent == that.parent - case _ => false - } + override def computeHash(h: Hashing) = h.withBinder(this).doHash(getClass, parent) - override def eql(that: Type) = that match { - case that: RecType => parent.eq(that.parent) + override def iso(that: Any, e: StructEquality) = that match { + case that: RecType => + e.withBinders(this, that).equals(parent, that.parent) case _ => false } @@ -2370,11 +2398,11 @@ object Types { // --- AndType/OrType --------------------------------------------------------------- - trait AndOrType extends ValueType { // todo: check where we can simplify using AndOrType + abstract class AndOrType extends CachedGroundType with ValueType { def tp1: Type def tp2: Type def isAnd: Boolean - def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type // needed? + def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type private[this] var myBaseClassesPeriod: Period = Nowhere private[this] var myBaseClasses: List[ClassSymbol] = _ @@ -2406,9 +2434,21 @@ object Types { } myBaseClasses } + + override def computeHash(h: Hashing) = h.doHash(getClass, tp1, tp2) + + override def eql(that: Type) = that match { + case that: AndOrType => isAnd == that.isAnd && tp1.eq(that.tp1) && tp2.eq(that.tp2) + case _ => false + } + + override def iso(that: Any, e: StructEquality) = that match { + case that: AndOrType => isAnd == that.isAnd && e.equals(tp1, that.tp1) && e.equals(tp2, that.tp2) + case _ => false + } } - abstract case class AndType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType { def isAnd = true @@ -2423,12 +2463,6 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedAndType(tp1, tp2) - override def computeHash = doHash(tp1, tp2) - - override def eql(that: Type) = that match { - case that: AndType => tp1.eq(that.tp1) && tp2.eq(that.tp2) - case _ => false - } } final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) @@ -2457,7 +2491,7 @@ object Types { if (checkValid) apply(tp1, tp2) else unchecked(tp1, tp2) } - abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType { assert(tp1.isInstanceOf[ValueTypeOrWildcard] && tp2.isInstanceOf[ValueTypeOrWildcard], s"$tp1 $tp2") @@ -2483,13 +2517,6 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedOrType(tp1, tp2) - - override def computeHash = doHash(tp1, tp2) - - override def eql(that: Type) = that match { - case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) - case _ => false - } } final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) @@ -2550,12 +2577,17 @@ object Types { def derivedExprType(resType: Type)(implicit ctx: Context) = if (resType eq this.resType) this else ExprType(resType) - override def computeHash = doHash(resType) + override def computeHash(h: Hashing) = h.doHash(getClass, resType) override def eql(that: Type) = that match { case that: ExprType => resType.eq(that.resType) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: ExprType => e.equals(resType, that.resType) + case _ => false + } } final class CachedExprType(resultType: Type) extends ExprType(resultType) @@ -2631,34 +2663,51 @@ object Types { final override def toString = s"$prefixString($paramNames, $paramInfos, $resType)" } + /** Base class of HKTypeLambda. In the future could be base class of HKTermLambda + * (aka typelevel function type) if that is added. + */ abstract class HKLambda extends CachedProxyType with LambdaType { final override def underlying(implicit ctx: Context) = resType - final override def computeHash = doHash(paramNames, resType, paramInfos) + final override def computeHash(h: Hashing) = + h.withBinder(this).doHash(getClass, paramNames, resType, paramInfos) - final override def equals(that: Any) = that match { + final override def iso(that: Any, e: StructEquality) = that match { case that: HKLambda => - paramNames == that.paramNames && - paramInfos == that.paramInfos && - resType == that.resType && - companion.eq(that.companion) + paramNames.eqElements(that.paramNames) && + companion.eq(that.companion) && { + val e1 = e.withBinders(this, that) + e1.equals(paramInfos, that.paramInfos) && + e1.equals(resType, that.resType) + } case _ => false } + } - final override def eql(that: Type) = that match { - case that: HKLambda => - paramNames.equals(that.paramNames) && - paramInfos.equals(that.paramInfos) && - resType.equals(that.resType) && - companion.eq(that.companion) - case _ => + /** Common base class of MethodType and PolyType. Its methods are duplicated + * from HKLambda for efficiency. Joining the two methods in the common supertrait + * LambdaType could be slower because of trait dispatch and because the type test + * in `iso` would be to a trait instead of a class + */ + abstract class MethodOrPoly extends CachedGroundType with LambdaType with MethodicType { + + final override def computeHash(h: Hashing) = + h.withBinder(this).doHash(getClass, paramNames, resType, paramInfos) + + final override def iso(that: Any, e: StructEquality) = that match { + case that: MethodOrPoly => + paramNames.eqElements(that.paramNames) && + companion.eq(that.companion) && { + val e1 = e.withBinders(this, that) + e1.equals(paramInfos, that.paramInfos) && + e1.equals(resType, that.resType) + } + case _ => false } } - trait MethodOrPoly extends LambdaType with MethodicType - trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ type ThisName = TermName @@ -2774,7 +2823,7 @@ object Types { abstract case class MethodType(paramNames: List[TermName])( paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type) - extends CachedGroundType with MethodOrPoly with TermLambda with NarrowCached { thisMethodType => + extends MethodOrPoly with TermLambda with NarrowCached { thisMethodType => import MethodType._ type This = MethodType @@ -2791,27 +2840,6 @@ object Types { def computeSignature(implicit ctx: Context): Signature = resultSignature.prepend(paramInfos, isJavaMethod) - final override def computeHash = doHash(paramNames, resType, paramInfos) - - final override def equals(that: Any) = that match { - case that: MethodType => - paramNames == that.paramNames && - paramInfos == that.paramInfos && - resType == that.resType && - companion.eq(that.companion) - case _ => - false - } - - final override def eql(that: Type) = that match { - case that: MethodType => - paramNames.eqElements(that.paramNames) && - paramInfos.eqElements(that.paramInfos) && - resType.eq(that.resType) && - companion.eq(that.companion) - case _ => - false - } protected def prefixString = "MethodType" } @@ -2970,7 +2998,7 @@ object Types { */ class PolyType(val paramNames: List[TypeName])( paramInfosExp: PolyType => List[TypeBounds], resultTypeExp: PolyType => Type) - extends UncachedGroundType with MethodOrPoly with TypeLambda { + extends MethodOrPoly with TypeLambda { type This = PolyType def companion = PolyType @@ -3129,12 +3157,18 @@ object Types { def derivedAppliedType(tycon: Type, args: List[Type])(implicit ctx: Context): Type = if ((tycon eq this.tycon) && (args eq this.args)) this else tycon.appliedTo(args) + + override def computeHash(h: Hashing) = h.doHash(getClass, tycon, args) + override def eql(that: Type) = this eq that // safe because applied types are hash-consed separately + + final override def iso(that: Any, e: StructEquality) = that match { + case that: AppliedType => e.equals(tycon, that.tycon) && e.equals(args, that.args) + case _ => false + } } final class CachedAppliedType(tycon: Type, args: List[Type], hc: Int) extends AppliedType(tycon, args) { myHash = hc - override def computeHash = unsupported("computeHash") - override def eql(that: Type) = this eq that // safe because applied types are hash-consed separately } object AppliedType { @@ -3163,10 +3197,10 @@ object Types { else infos(paramNum) } - override def computeHash = doHash(paramNum, binder.identityHash) + override def computeHash(h: Hashing) = h.doHash(getClass, paramNum, h.identityHash(binder)) - override def equals(that: Any) = that match { - case that: ParamRef => binder.eq(that.binder) && paramNum == that.paramNum + override def iso(that: Any, e: StructEquality) = that match { + case that: ParamRef => e.equalBinders(binder, that.binder) && paramNum == that.paramNum case _ => false } @@ -3220,10 +3254,10 @@ object Types { // need to customize hashCode and equals to prevent infinite recursion // between RecTypes and RecRefs. - override def computeHash = addDelta(binder.identityHash, 41) + override def computeHash(h: Hashing) = h.addDelta(h.identityHash(binder), 41) - override def equals(that: Any) = that match { - case that: RecThis => binder.eq(that.binder) + override def iso(that: Any, e: StructEquality) = that match { + case that: RecThis => e.equalBinders(binder, that.binder) case _ => false } @@ -3241,8 +3275,7 @@ object Types { override def underlying(implicit ctx: Context) = info def derivedSkolemType(info: Type)(implicit ctx: Context) = if (info eq this.info) this else SkolemType(info) - override def hashCode: Int = identityHash - override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) + override def hashCode: Int = System.identityHashCode(this) def withName(name: Name): this.type = { myRepr = name; this } @@ -3276,6 +3309,7 @@ object Types { * * `owningTree` and `owner` are used to determine whether a type-variable can be instantiated * at some given point. See `Inferencing#interpolateUndetVars`. + * ??? make uncached ??? */ final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, val bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType { @@ -3347,8 +3381,7 @@ object Types { } } - override def computeHash: Int = identityHash - override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) + override def computeHash(h: Hashing): Int = h.identityHash(this) override def toString = { def instStr = if (inst.exists) s" -> $inst" else "" @@ -3422,7 +3455,7 @@ object Types { if ((prefix eq this.prefix) && (classParents eq this.classParents) && (decls eq this.decls) && (selfInfo eq this.selfInfo)) this else ClassInfo(prefix, cls, classParents, decls, selfInfo) - override def computeHash = doHash(cls, prefix) + override def computeHash(h: Hashing) = h.doHash(getClass, cls, prefix) override def eql(that: Type) = that match { case that: ClassInfo => @@ -3434,6 +3467,16 @@ object Types { case _ => false } + override def iso(that: Any, e: StructEquality) = that match { + case that: ClassInfo => + e.equals(prefix, that.prefix) && + cls.eq(that.cls) && + e.equals(classParents, that.classParents) && + decls.eq(that.decls) && + selfInfo.eq(that.selfInfo) + case _ => false + } + override def toString = s"ClassInfo($prefix, $cls, $classParents)" } @@ -3505,11 +3548,11 @@ object Types { case _ => super.| (that) } - override def computeHash = doHash(lo, hi) + override def computeHash(h: Hashing) = h.doHash(getClass, lo, hi) - override def equals(that: Any): Boolean = that match { + override def iso(that: Any, e: StructEquality): Boolean = that match { case that: TypeAlias => false - case that: TypeBounds => lo == that.lo && hi == that.hi + case that: TypeBounds => e.equals(lo, that.lo) && e.equals(hi, that.hi) case _ => false } @@ -3528,10 +3571,10 @@ object Types { def derivedTypeAlias(alias: Type)(implicit ctx: Context) = if (alias eq this.alias) this else TypeAlias(alias) - override def computeHash = doHash(alias) + override def computeHash(h: Hashing) = h.doHash(getClass, alias) - override def equals(that: Any): Boolean = that match { - case that: TypeAlias => alias == that.alias + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: TypeAlias => e.equals(alias, that.alias) case _ => false } @@ -3573,6 +3616,11 @@ object Types { derivedAnnotatedType(tpe.stripTypeVar, annot) override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: AnnotatedType => e.equals(tpe, that.tpe) && (annot `eq` that.annot) + case _ => false + } } object AnnotatedType { @@ -3587,12 +3635,17 @@ object Types { def derivedJavaArrayType(elemtp: Type)(implicit ctx: Context) = if (elemtp eq this.elemType) this else JavaArrayType(elemtp) - override def computeHash = doHash(elemType) + override def computeHash(h: Hashing) = h.doHash(getClass, elemType) override def eql(that: Type) = that match { case that: JavaArrayType => elemType.eq(that.elemType) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: JavaArrayType => e.equals(elemType, that.elemType) + case _ => false + } } final class CachedJavaArrayType(elemType: Type) extends JavaArrayType(elemType) object JavaArrayType { @@ -3605,12 +3658,12 @@ object Types { /** Sentinel for "missing type" */ @sharable case object NoType extends CachedGroundType { override def exists = false - override def computeHash = hashSeed + override def computeHash(h: Hashing) = getClass.hashCode } /** Missing prefix */ @sharable case object NoPrefix extends CachedGroundType { - override def computeHash = hashSeed + override def computeHash(h: Hashing) = getClass.hashCode } /** A common superclass of `ErrorType` and `TryDynamicCallSite`. Instances of this @@ -3650,12 +3703,17 @@ object Types { else if (!optBounds.exists) WildcardType else WildcardType(optBounds.asInstanceOf[TypeBounds]) - override def computeHash = doHash(optBounds) + override def computeHash(h: Hashing) = h.doHash(getClass, optBounds) override def eql(that: Type) = that match { case that: WildcardType => optBounds.eq(that.optBounds) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: WildcardType => e.equals(optBounds, that.optBounds) + case _ => false + } } final class CachedWildcardType(optBounds: Type) extends WildcardType(optBounds) diff --git a/compiler/src/dotty/tools/dotc/core/Uniques.scala b/compiler/src/dotty/tools/dotc/core/Uniques.scala index 36228f418b9e..d94bfa526c4d 100644 --- a/compiler/src/dotty/tools/dotc/core/Uniques.scala +++ b/compiler/src/dotty/tools/dotc/core/Uniques.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc package core -import Types._, Symbols._, Contexts._, util.Stats._, Hashable._, Names._ +import Types._, Symbols._, Contexts._, util.Stats._, Hashing._, Names._ import config.Config import Decorators._ import util.HashSet @@ -41,7 +41,7 @@ object Uniques { ) */ - final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity) with Hashable { + final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity) { override def hash(x: NamedType): Int = x.hash private def findPrevious(h: Int, prefix: Type, designator: Designator): NamedType = { @@ -54,7 +54,7 @@ object Uniques { } def enterIfNew(prefix: Type, designator: Designator, isTerm: Boolean)(implicit ctx: Context): NamedType = { - val h = doHash(designator, prefix) + val h = Hashing.doHash(classOf[NamedType], designator, prefix) if (monitored) recordCaching(h, classOf[NamedType]) def newType = if (isTerm) new CachedTermRef(prefix, designator, h) @@ -67,7 +67,7 @@ object Uniques { } } - final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity) with Hashable { + final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity) { override def hash(x: AppliedType): Int = x.hash private def findPrevious(h: Int, tycon: Type, args: List[Type]): AppliedType = { @@ -80,7 +80,7 @@ object Uniques { } def enterIfNew(tycon: Type, args: List[Type]): AppliedType = { - val h = doHash(tycon, args) + val h = Hashing.doHash(classOf[AppliedType], tycon, args) def newType = new CachedAppliedType(tycon, args, h) if (monitored) recordCaching(h, classOf[CachedAppliedType]) if (h == NotCached) newType diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index bceb0c447067..95f79815505c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -29,7 +29,6 @@ import ErrorReporting._ import reporting.diagnostic.{Message, MessageContainer} import Inferencing.fullyDefinedType import Trees._ -import Hashable._ import util.Property import config.Config import config.Printers.{implicits, implicitsDetailed, typr} @@ -210,7 +209,7 @@ object Implicits { /** The implicit references that are eligible for type `tp`. */ def eligible(tp: Type): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ { - if (tp.hash == NotCached) computeEligible(tp) + if (tp.hash == Hashing.NotCached) computeEligible(tp) else eligibleCache get tp match { case Some(eligibles) => def elided(ci: ContextualImplicits): Int = { @@ -469,7 +468,7 @@ trait ImplicitRunInfo { self: Run => * @param isLifted Type `tp` is the result of a `liftToClasses` application */ def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = { - val canCache = Config.cacheImplicitScopes && tp.hash != NotCached + val canCache = Config.cacheImplicitScopes && tp.hash != Hashing.NotCached def computeIScope() = { val savedEphemeral = ctx.typerState.ephemeral ctx.typerState.ephemeral = false @@ -779,7 +778,7 @@ trait Implicits { self: Typer => case result: SearchSuccess => result.tstate.commit() implicits.println(i"success: $result") - implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}") + implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint}${ctx.typerState.hashesStr(implicits)}") result case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index b975fe51c198..764900f3e5be 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -118,9 +118,12 @@ object ProtoTypes { if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this else SelectionProto(name, memberProto, compat, privateOK) - override def equals(that: Any): Boolean = that match { + override def iso(that: Any, e: StructEquality): Boolean = that match { case that: SelectionProto => - (name eq that.name) && (memberProto == that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) + (name `eq` that.name) && + e.equals(memberProto, that.memberProto) && + (compat `eq` that.compat) && + (privateOK == that.privateOK) case _ => false } @@ -130,9 +133,9 @@ object ProtoTypes { override def deepenProto(implicit ctx: Context) = derivedSelectionProto(name, memberProto.deepenProto, compat) - override def computeHash = { + override def computeHash(h: Hashing) = { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) - addDelta(doHash(name, memberProto), delta) + h.addDelta(h.doHash(getClass, name, memberProto), delta) } } @@ -326,7 +329,7 @@ object ProtoTypes { } class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) { - override def computeHash = doHash(argType, resultType) + override def computeHash(h: Hashing) = h.doHash(getClass, argType, resultType) } object ViewProto { @@ -397,9 +400,17 @@ object ProtoTypes { tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner)) } - val added = - if (state.constraint contains tl) tl.newLikeThis(tl.paramNames, tl.paramInfos, tl.resultType) + /** Ensure that `tl` is not already in constraint. If necessary, + * make a copy of `tl` by turning one of the bounds into a `LazyRef` + */ + def ensureFresh(tl: TypeLambda): TypeLambda = + if (state.constraint contains tl) { + val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos + val newParamInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 + ensureFresh(tl.newLikeThis(tl.paramNames, newParamInfos, tl.resultType)) + } else tl + val added = ensureFresh(tl) val tvars = if (addTypeVars) newTypeVars(added) else Nil ctx.typeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) (added, tvars) diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala index 5123cb34a841..2632cfe181d3 100644 --- a/compiler/test/dotc/tests.scala +++ b/compiler/test/dotc/tests.scala @@ -310,7 +310,7 @@ class tests extends CompilerTest { @Test def tasty_core = compileList("tasty_core", List( "Annotations.scala", "Constants.scala", "Constraint.scala", "ConstraintHandling.scala", "ConstraintRunInfo.scala", "Contexts.scala", "Decorators.scala", "Definitions.scala", - "DenotTransformers.scala", "Denotations.scala", "Flags.scala", "Hashable.scala", + "DenotTransformers.scala", "Denotations.scala", "Flags.scala", "Hashing.scala", "NameOps.scala", "Names.scala", "OrderingConstraint.scala", "Periods.scala", "Phases.scala", "Scopes.scala", "Signature.scala", "StdNames.scala", "Substituters.scala", "SymDenotations.scala", "SymbolLoaders.scala", "Symbols.scala", diff --git a/tests/pos/i3965.scala b/tests/pos/i3965.scala new file mode 100644 index 000000000000..e5aec615e71d --- /dev/null +++ b/tests/pos/i3965.scala @@ -0,0 +1,19 @@ +trait Iterable[+A] extends IterableOps[A, Iterable, Iterable[A]] +trait IterableOps[+A, +CCop[_], +C] + +trait SortedSet[A] extends Iterable[A] with SortedSetOps[A, SortedSet, SortedSet[A]] + +trait SortedSetOps[A, +CCss[X] <: SortedSet[X], +C <: SortedSetOps[A, CCss, C]] + +class TreeSet[A] + extends SortedSet[A] + with SortedSetOps[A, TreeSet, TreeSet[A]] + +class Test { + def optionSequence1[CCos[X] <: IterableOps[X, CCos, _], A](xs: CCos[Option[A]]): Option[CCos[A]] = ??? + def optionSequence1[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], A : Ordering](xs: CC[Option[A]]): Option[CC[A]] = ??? + + def test(xs2: TreeSet[Option[String]]) = { + optionSequence1(xs2) + } +} diff --git a/tests/pos/i3965a.scala b/tests/pos/i3965a.scala new file mode 100644 index 000000000000..eff026f951d1 --- /dev/null +++ b/tests/pos/i3965a.scala @@ -0,0 +1,15 @@ +trait SortedSet[A] extends SortedSetOps[A, SortedSet, SortedSet[A]] + +trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + +class TreeSet[A] + extends SortedSet[A] + with SortedSetOps[A, TreeSet, TreeSet[A]] + +class Test { + def optionSequence1[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], A : Ordering](xs: CC[A]): Unit = () + + def test(xs2: TreeSet[String]) = { + optionSequence1(xs2) + } +}