diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a0eb74be1422..c8d2595898b0 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -306,8 +306,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def prefixIsElidable(tp: NamedType)(implicit ctx: Context) = { val typeIsElidable = tp.prefix match { - case NoPrefix => - true case pre: ThisType => pre.cls.isStaticOwner || tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls @@ -316,8 +314,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { // eg anonymous TypeMap inside TypeMap.andThen case pre: TermRef => pre.symbol.is(Module) && pre.symbol.isStatic - case _ => - false + case pre => + pre `eq` NoPrefix } typeIsElidable || tp.symbol.is(JavaStatic) || diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 0fdd8c63c259..89d0bef4ba14 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -617,7 +617,8 @@ object Contexts { "uniqueNamedTypes" -> uniqueNamedTypes) /** A map that associates label and size of all uniques sets */ - def uniquesSizes: Map[String, Int] = uniqueSets.mapValues(_.size) + def uniquesSizes: Map[String, (Int, Int, Int)] = + uniqueSets.mapValues(s => (s.size, s.accesses, s.misses)) /** Number of findMember calls on stack */ private[core] var findMemberCount: Int = 0 diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index e4510c53e6c1..188c9bf0cb66 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -3,8 +3,18 @@ package core import Types._ import scala.util.hashing.{ MurmurHash3 => hashing } +import annotation.tailrec object Hashable { + + /** A null terminated list of BindingTypes. We use `null` here for efficiency */ + class Binders(val tp: BindingType, val next: Binders) + + /** A null terminated list of pairs of BindingTypes. Used for isomorphism tests. */ + class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs) { + @tailrec final def matches(t1: Type, t2: Type): Boolean = + (t1 `eq` tp1) && (t2 `eq` tp2) || next != null && next.matches(t1, t2) + } /** A hash value indicating that the underlying type is not * cached in uniques. @@ -33,26 +43,29 @@ trait Hashable { protected final def finishHash(hashCode: Int, arity: Int): Int = avoidSpecialHashes(hashing.finalizeHash(hashCode, arity)) - final def identityHash = avoidSpecialHashes(System.identityHashCode(this)) + final def typeHash(bs: Binders, tp: Type) = + if (bs == null || tp.stableHash) tp.hash else tp.computeHash(bs) + + def identityHash(bs: Binders) = avoidSpecialHashes(System.identityHashCode(this)) - protected def finishHash(seed: Int, arity: Int, tp: Type): Int = { - val elemHash = tp.hash + protected def finishHash(bs: Binders, seed: Int, arity: Int, tp: Type): Int = { + val elemHash = typeHash(bs, 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 = tp1.hash + protected def finishHash(bs: Binders, seed: Int, arity: Int, tp1: Type, tp2: Type): Int = { + val elemHash = typeHash(bs, tp1) if (elemHash == NotCached) return NotCached - finishHash(hashing.mix(seed, elemHash), arity + 1, tp2) + finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tp2) } - protected def finishHash(seed: Int, arity: Int, tps: List[Type]): Int = { + protected def finishHash(bs: Binders, 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 + val elemHash = typeHash(bs, xs.head) if (elemHash == NotCached) return NotCached h = hashing.mix(h, elemHash) xs = xs.tail @@ -61,33 +74,33 @@ trait Hashable { finishHash(h, len) } - protected def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = { - val elemHash = tp.hash + protected def finishHash(bs: Binders, seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = { + val elemHash = typeHash(bs, tp) if (elemHash == NotCached) return NotCached - finishHash(hashing.mix(seed, elemHash), arity + 1, tps) + finishHash(bs, 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(bs: Binders, tp: Type): Int = + finishHash(bs, hashSeed, 0, tp) - protected final def doHash(tp1: Type, tp2: Type): Int = - finishHash(hashSeed, 0, tp1, tp2) + protected final def doHash(bs: Binders, x1: Any, tp2: Type): Int = + finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, 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(bs: Binders, tp1: Type, tp2: Type): Int = + finishHash(bs, hashSeed, 0, tp1, tp2) - protected final def doHash(tp1: Type, tps2: List[Type]): Int = - finishHash(hashSeed, 0, tp1, tps2) + protected final def doHash(bs: Binders, x1: Any, tp2: Type, tp3: Type): Int = + finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3) - 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(bs: Binders, tp1: Type, tps2: List[Type]): Int = + finishHash(bs, hashSeed, 0, tp1, tps2) + protected final def doHash(bs: Binders, x1: Any, tp2: Type, tps3: List[Type]): Int = + finishHash(bs, 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) @@ -96,7 +109,7 @@ trait Hashable { if (elemHash == NotCached) NotCached else avoidSpecialHashes(elemHash + delta) - private def avoidSpecialHashes(h: Int) = + protected def avoidSpecialHashes(h: Int) = if (h == NotCached) NotCachedAlt else if (h == HashUnknown) HashUnknownAlt else h diff --git a/compiler/src/dotty/tools/dotc/core/Substituters.scala b/compiler/src/dotty/tools/dotc/core/Substituters.scala index e78dd9a7bd86..9faff4b661f6 100644 --- a/compiler/src/dotty/tools/dotc/core/Substituters.scala +++ b/compiler/src/dotty/tools/dotc/core/Substituters.scala @@ -12,9 +12,9 @@ trait Substituters { this: Context => case tp: BoundType => if (tp.binder eq from) tp.copyBoundType(to.asInstanceOf[tp.BT]) else tp case tp: NamedType => - if (tp.currentSymbol.isStatic) tp + if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(subst(tp.prefix, from, to, theMap)) - case _: ThisType | NoPrefix => + case _: ThisType => tp case _ => (if (theMap != null) theMap else new SubstBindingMap(from, to)) @@ -26,9 +26,9 @@ trait Substituters { this: Context => case tp: NamedType => val sym = tp.symbol if (sym eq from) return to - if (sym.isStatic && !from.isStatic) tp + if (sym.isStatic && !from.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(subst1(tp.prefix, from, to, theMap)) - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case _ => (if (theMap != null) theMap else new Subst1Map(from, to)) @@ -42,9 +42,9 @@ trait Substituters { this: Context => val sym = tp.symbol if (sym eq from1) return to1 if (sym eq from2) return to2 - if (sym.isStatic && !from1.isStatic && !from2.isStatic) tp + if (sym.isStatic && !from1.isStatic && !from2.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(subst2(tp.prefix, from1, to1, from2, to2, theMap)) - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case _ => (if (theMap != null) theMap else new Subst2Map(from1, to1, from2, to2)) @@ -63,9 +63,9 @@ trait Substituters { this: Context => fs = fs.tail ts = ts.tail } - if (sym.isStatic && !existsStatic(from)) tp + if (sym.isStatic && !existsStatic(from) || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(subst(tp.prefix, from, to, theMap)) - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case _ => (if (theMap != null) theMap else new SubstMap(from, to)) @@ -84,7 +84,7 @@ trait Substituters { this: Context => fs = fs.tail ts = ts.tail } - if (sym.isStatic && !existsStatic(from)) tp + if (sym.isStatic && !existsStatic(from) || (tp.prefix `eq` NoPrefix)) tp else { tp.info match { case TypeAlias(alias) => @@ -94,7 +94,7 @@ trait Substituters { this: Context => } tp.derivedSelect(substDealias(tp.prefix, from, to, theMap)) } - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case _ => (if (theMap != null) theMap else new SubstDealiasMap(from, to)) @@ -114,7 +114,7 @@ trait Substituters { this: Context => fs = fs.tail ts = ts.tail } - if (sym.isStatic && !existsStatic(from)) tp + if (sym.isStatic && !existsStatic(from) || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(substSym(tp.prefix, from, to, theMap)) case tp: ThisType => val sym = tp.cls @@ -126,7 +126,7 @@ trait Substituters { this: Context => ts = ts.tail } tp - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case _ => (if (theMap != null) theMap else new SubstSymMap(from, to)) @@ -138,9 +138,9 @@ trait Substituters { this: Context => case tp: ThisType => if (tp.cls eq from) to else tp case tp: NamedType => - if (tp.currentSymbol.isStaticOwner) tp + if (tp.currentSymbol.isStaticOwner || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(substThis(tp.prefix, from, to, theMap)) - case _: BoundType | NoPrefix => + case _: BoundType => tp case _ => (if (theMap != null) theMap else new SubstThisMap(from, to)) @@ -152,9 +152,9 @@ trait Substituters { this: Context => case tp @ RecThis(binder) => if (binder eq from) to else tp case tp: NamedType => - if (tp.currentSymbol.isStatic) tp + if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(substRecThis(tp.prefix, from, to, theMap)) - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case _ => (if (theMap != null) theMap else new SubstRecThisMap(from, to)) @@ -166,9 +166,9 @@ trait Substituters { this: Context => case tp: BoundType => if (tp == from) to else tp case tp: NamedType => - if (tp.currentSymbol.isStatic) tp + if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(substParam(tp.prefix, from, to, theMap)) - case _: ThisType | NoPrefix => + case _: ThisType => tp case _ => (if (theMap != null) theMap else new SubstParamMap(from, to)) @@ -180,9 +180,9 @@ trait Substituters { this: Context => case tp: ParamRef => if (tp.binder == from) to(tp.paramNum) else tp case tp: NamedType => - if (tp.currentSymbol.isStatic) tp + if (tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(substParams(tp.prefix, from, to, theMap)) - case _: ThisType | NoPrefix => + case _: ThisType => tp case _ => (if (theMap != null) theMap else new SubstParamsMap(from, to)) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 78643d714506..757c6490e275 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -461,7 +461,7 @@ object SymDenotations { /** Is symbol known to not exist? */ final def isAbsent(implicit ctx: Context): Boolean = { ensureCompleted() - myInfo == NoType || + (myInfo `eq` NoType) || (this is (ModuleVal, butNot = Package)) && moduleClass.isAbsent } @@ -1296,7 +1296,7 @@ object SymDenotations { private[this] var myMemberCachePeriod: Period = Nowhere /** A cache from types T to baseType(T, C) */ - type BaseTypeMap = java.util.HashMap[CachedType, Type] + type BaseTypeMap = java.util.IdentityHashMap[CachedType, Type] private[this] var myBaseTypeCache: BaseTypeMap = null private[this] var myBaseTypeCachePeriod: Period = Nowhere @@ -1720,7 +1720,7 @@ object SymDenotations { btrCache.put(tp, basetp) } else btrCache.remove(tp) - } else if (basetp == NoPrefix) + } else if (basetp `eq` NoPrefix) throw CyclicReference(this) basetp } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 05dc572dc51e..17e6108416fb 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(bs: Hashable.Binders) = doHash(bs, tycon, erasedUnderlying) } final class CachedErasedValueType(tycon: TypeRef, erasedUnderlying: Type) @@ -429,10 +429,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean tp.derivedClassInfo(NoPrefix, erasedParents, erasedDecls, erasedRef(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } - case NoType | NoPrefix | _: ErrorType | JavaArrayType(_) => + case _: ErrorType | JavaArrayType(_) => tp case tp: WildcardType if wildcardOK => tp + case tp if (tp `eq` NoType) || (tp `eq` NoPrefix) => + tp } private def eraseArray(tp: Type)(implicit ctx: Context) = { diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index b0df2d7feec0..4fa3dcea4bf3 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -57,11 +57,11 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tp match { case tp: NamedType => val sym = tp.symbol - if (sym.isStatic) tp + if (sym.isStatic || (tp.prefix `eq` NoPrefix)) tp else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix))) case tp: ThisType => toPrefix(pre, cls, tp.cls) - case _: BoundType | NoPrefix => + case _: BoundType => tp case _ => mapOver(tp) @@ -80,7 +80,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Implementation of Types#simplified */ final def simplify(tp: Type, theMap: SimplifyMap): Type = tp match { case tp: NamedType => - if (tp.symbol.isStatic) tp + if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(simplify(tp.prefix, theMap)) match { case tp1: NamedType if tp1.denotationIsCurrent => val tp2 = tp1.reduceProjection @@ -97,7 +97,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. val tvar = typerState.constraint.typeVarOfParam(tp) if (tvar.exists) tvar else tp } - case _: ThisType | _: BoundType | NoPrefix => + case _: ThisType | _: BoundType => tp case tp: TypeAlias => tp.derivedTypeAlias(simplify(tp.alias, theMap)) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 52ce035ed327..b6c6b1e79209 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1399,15 +1399,35 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) + /** Compare `this == that`, assuming corresponding binders in `bs` are equal. + * The normal `equals` should be equivalent to `equals(that, null`)`. + * We usually override `equals` when we override `iso` except if the + * `equals` comes from a case class, so it already has the right definition anyway. + */ + final def equals(that: Any, bs: BinderPairs): Boolean = + (this `eq` that.asInstanceOf[AnyRef]) || this.iso(that, bs) + + /** Is `this` isomorphic to `that`, assuming pairs of matching binders `bs`? + * It is assumed that `this.ne(that)`. + */ + protected def iso(that: Any, bs: BinderPairs): Boolean = this.equals(that) + + /** Equality used for hash-consing; uses `eq` on all recursive invocations, + * except where a BindingType is involved. The latter demand a deep isomorphism check. + */ + 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 hashcode relative to enclosing binders `bs` */ + def computeHash(bs: Binders): Int + + /** Is the `hash` of this type the same for all possible sequences of enclosing binders? */ + def stableHash: Boolean = true } // end Type @@ -1445,14 +1465,13 @@ object Types { private[this] var myHash = HashUnknown final def hash = { if (myHash == HashUnknown) { - myHash = computeHash + myHash = computeHash(null) assert(myHash != HashUnknown) } myHash } override final def hashCode = if (hash == NotCached) System.identityHashCode(this) else hash - def computeHash: Int } /** Instances of this class are cached and are proxies. */ @@ -1460,19 +1479,19 @@ object Types { protected[this] var myHash = HashUnknown final def hash = { if (myHash == HashUnknown) { - myHash = computeHash + myHash = computeHash(null) assert(myHash != HashUnknown) } myHash } override final def hashCode = if (hash == NotCached) System.identityHashCode(this) else hash - def computeHash: Int } /** Instances of this class are uncached and are not proxies. */ abstract class UncachedGroundType extends Type { final def hash = NotCached + final def computeHash(bs: Binders) = NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") @@ -1482,6 +1501,7 @@ object Types { /** Instances of this class are uncached and are proxies. */ abstract class UncachedProxyType extends TypeProxy { final def hash = NotCached + final def computeHash(bs: Binders) = NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") @@ -1512,10 +1532,27 @@ object Types { def isOverloaded(implicit ctx: Context) = false } - /** A marker trait for types that bind other types that refer to them. + /** A trait for types that bind other types that refer to them. * Instances are: LambdaType, RecType. */ - trait BindingType extends Type + trait BindingType extends Type { + + /** If this type is in `bs`, a hashcode based on its position in `bs`. + * Otherise the standard identity hash. + */ + override def identityHash(bs: Binders) = { + def recur(n: Int, tp: BindingType, rest: Binders): Int = + if (this `eq` tp) finishHash(hashing.mix(hashSeed, n), 1) + else if (rest == null) System.identityHashCode(this) + else recur(n + 1, rest.tp, rest.next) + avoidSpecialHashes( + if (bs == null) System.identityHashCode(this) + else recur(1, bs.tp, bs.next)) + } + + def equalBinder(that: BindingType, bs: BinderPairs): Boolean = + (this `eq` that) || bs != null && bs.matches(this, that) + } /** A trait for proto-types, used as expected types in typer */ trait ProtoType extends Type { @@ -1551,6 +1588,7 @@ object Types { private[this] var lastDenotation: Denotation = null private[this] var lastSymbol: Symbol = null private[this] var checkedPeriod: Period = Nowhere + private[this] var myStableHash: Byte = 0 // Invariants: // (1) checkedPeriod != Nowhere => lastDenotation != null @@ -2017,15 +2055,22 @@ object Types { } } - override def equals(that: Any) = that match { + override def equals(that: Any) = equals(that, null) + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { case that: NamedType => - this.designator == that.designator && - this.prefix == that.prefix + designator.equals(that.designator) && + prefix.equals(that.prefix, bs) case _ => false } - override def computeHash = unsupported("computeHash") + override def computeHash(bs: Binders) = doHash(bs, designator, prefix) + + override def stableHash = { + if (myStableHash == 0) myStableHash = if (prefix.stableHash) 1 else -1 + myStableHash > 0 + } override def eql(that: Type) = this eq that // safe because named types are hash-consed separately } @@ -2149,7 +2194,7 @@ object Types { // can happen in IDE if `cls` is stale } - override def computeHash = doHash(tref) + override def computeHash(bs: Binders) = doHash(bs, tref) override def eql(that: Type) = that match { case that: ThisType => tref.eq(that.tref) @@ -2177,7 +2222,7 @@ 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(bs: Binders) = doHash(bs, thistpe, supertpe) override def eql(that: Type) = that match { case that: SuperType => thistpe.eq(that.thistpe) && supertpe.eq(that.supertpe) @@ -2198,7 +2243,7 @@ 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(bs: Binders) = doHash(value) } final class CachedConstantType(value: Constant) extends ConstantType(value) @@ -2262,7 +2307,8 @@ object Types { if (parent.member(refinedName).exists) derivedRefinedType(parent, refinedName, refinedInfo) else parent - override def computeHash = doHash(refinedName, refinedInfo, parent) + override def computeHash(bs: Binders) = doHash(bs, refinedName, refinedInfo, parent) + override def stableHash = refinedInfo.stableHash && parent.stableHash override def eql(that: Type) = that match { case that: RefinedType => @@ -2271,6 +2317,15 @@ object Types { parent.eq(that.parent) case _ => false } + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { + case that: RefinedType => + refinedName.eq(that.refinedName) && + refinedInfo.equals(that.refinedInfo, bs) && + parent.equals(that.parent, bs) + case _ => false + } + // equals comes from case class; no matching override is needed } class CachedRefinedType(parent: Type, refinedName: Name, refinedInfo: Type) @@ -2326,15 +2381,20 @@ object Types { refacc.apply(false, tp) } - override def computeHash = doHash(parent) + override def computeHash(bs: Binders) = doHash(new Binders(this, bs), parent) - override def equals(that: Any) = that match { - case that: RecType => parent == that.parent - case _ => false - } + override def stableHash = false + // this is a conservative observation. By construction RecTypes contain at least + // one RecThis occurrence. Since `stableHash` does not keep track of enclosing + // bound types, it will return "unstable" for this occurrence and this would propagate. - override def eql(that: Type) = that match { - case that: RecType => parent.eq(that.parent) + // No definition of `eql` --> fall back on equals, which calls iso + + override def equals(that: Any): Boolean = equals(that, null) + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: RecType => + parent.equals(that.parent, new BinderPairs(this, that, bs)) case _ => false } @@ -2379,7 +2439,7 @@ 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 @@ -2415,9 +2475,23 @@ object Types { } myBaseClasses } + + override def computeHash(bs: Binders) = doHash(bs, tp1, tp2) + override def stableHash = tp1.stableHash && tp2.stableHash + + 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, bs: BinderPairs) = that match { + case that: AndOrType => isAnd == that.isAnd && tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs) + case _ => false + } + // equals comes from case classes; no matching override is needed } - 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 @@ -2431,13 +2505,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) @@ -2466,7 +2533,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") @@ -2492,13 +2559,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) @@ -2559,12 +2619,19 @@ 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(bs: Binders) = doHash(bs, resType) + override def stableHash = resType.stableHash override def eql(that: Type) = that match { case that: ExprType => resType.eq(that.resType) case _ => false } + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: ExprType => resType.equals(that.resType, bs) + case _ => false + } + // equals comes from case class; no matching override is needed } final class CachedExprType(resultType: Type) extends ExprType(resultType) @@ -2643,31 +2710,48 @@ object Types { abstract class HKLambda extends CachedProxyType with LambdaType { final override def underlying(implicit ctx: Context) = resType - final override def computeHash = doHash(paramNames, resType, paramInfos) + override def computeHash(bs: Binders) = + doHash(new Binders(this, bs), paramNames, resType, paramInfos) + + override def stableHash = resType.stableHash && paramInfos.stableHash + + final override def equals(that: Any) = equals(that, null) + + // No definition of `eql` --> fall back on equals, which calls iso - final override def equals(that: Any) = that match { + final override def iso(that: Any, bs: BinderPairs) = 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 bs1 = new BinderPairs(this, that, bs) + paramInfos.equalElements(that.paramInfos, bs1) && + resType.equals(that.resType, bs1) + } 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) + abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType { + final override def hashCode = System.identityHashCode(this) + + final override def equals(that: Any) = equals(that, null) + + // No definition of `eql` --> fall back on equals, which is `eq` + + final override def iso(that: Any, bs: BinderPairs) = that match { + case that: MethodOrPoly => + paramNames.eqElements(that.paramNames) && + companion.eq(that.companion) && { + val bs1 = new BinderPairs(this, that, bs) + paramInfos.equalElements(that.paramInfos, bs1) && + resType.equals(that.resType, bs1) + } case _ => false } } - trait MethodOrPoly extends LambdaType with MethodicType - trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ type ThisName = TermName @@ -2783,7 +2867,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 @@ -2800,28 +2884,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" } @@ -2979,7 +3041,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 @@ -3095,6 +3157,7 @@ object Types { private[this] var validSuper: Period = Nowhere private[this] var cachedSuper: Type = _ + private[this] var myStableHash: Byte = 0 override def underlying(implicit ctx: Context): Type = tycon @@ -3138,12 +3201,25 @@ 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(bs: Binders) = doHash(bs, tycon, args) + + override def stableHash = { + if (myStableHash == 0) myStableHash = if (tycon.stableHash && args.stableHash) 1 else -1 + myStableHash > 0 + } + + override def eql(that: Type) = this `eq` that // safe because applied types are hash-consed separately + + final override def iso(that: Any, bs: BinderPairs) = that match { + case that: AppliedType => tycon.equals(that.tycon, bs) && args.equalElements(that.args, bs) + case _ => false + } + // equals comes from case class; no matching override is needed } 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 { @@ -3159,6 +3235,7 @@ object Types { type BT <: Type val binder: BT def copyBoundType(bt: BT): Type + override def stableHash = false } abstract class ParamRef extends BoundType { @@ -3172,10 +3249,12 @@ object Types { else infos(paramNum) } - override def computeHash = doHash(paramNum, binder.identityHash) + override def computeHash(bs: Binders) = doHash(paramNum, binder.identityHash(bs)) + + override def equals(that: Any) = equals(that, null) - override def equals(that: Any) = that match { - case that: ParamRef => binder.eq(that.binder) && paramNum == that.paramNum + override def iso(that: Any, bs: BinderPairs) = that match { + case that: ParamRef => paramNum == that.paramNum && binder.equalBinder(that.binder, bs) case _ => false } @@ -3229,10 +3308,12 @@ 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(bs: Binders) = addDelta(binder.identityHash(bs), 41) - override def equals(that: Any) = that match { - case that: RecThis => binder.eq(that.binder) + override def equals(that: Any) = equals(that, null) + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: RecThis => binder.equalBinder(that.binder, bs) case _ => false } @@ -3250,7 +3331,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 hashCode: Int = System.identityHashCode(this) override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) def withName(name: Name): this.type = { myRepr = name; this } @@ -3356,7 +3437,7 @@ object Types { } } - override def computeHash: Int = identityHash + override def computeHash(bs: Binders): Int = identityHash(bs) override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) override def toString = { @@ -3431,7 +3512,8 @@ 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(bs: Binders) = doHash(bs, cls, prefix) + override def stableHash = prefix.stableHash && classParents.stableHash override def eql(that: Type) = that match { case that: ClassInfo => @@ -3443,6 +3525,18 @@ object Types { case _ => false } + override def equals(that: Any): Boolean = equals(that, null) + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: ClassInfo => + prefix.equals(that.prefix, bs) && + cls.eq(that.cls) && + classParents.equalElements(that.classParents, bs) && + decls.eq(that.decls) && + selfInfo.eq(that.selfInfo) + case _ => false + } + override def toString = s"ClassInfo($prefix, $cls, $classParents)" } @@ -3514,11 +3608,14 @@ object Types { case _ => super.| (that) } - override def computeHash = doHash(lo, hi) + override def computeHash(bs: Binders) = doHash(bs, lo, hi) + override def stableHash = lo.stableHash && hi.stableHash - override def equals(that: Any): Boolean = that match { + override def equals(that: Any): Boolean = equals(that, null) + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { case that: TypeAlias => false - case that: TypeBounds => lo == that.lo && hi == that.hi + case that: TypeBounds => lo.equals(that.lo, bs) && hi.equals(that.hi, bs) case _ => false } @@ -3537,12 +3634,14 @@ 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(bs: Binders) = doHash(bs, alias) + override def stableHash = alias.stableHash - override def equals(that: Any): Boolean = that match { - case that: TypeAlias => alias == that.alias + override def iso(that: Any, bs: BinderPairs): Boolean = that match { + case that: TypeAlias => alias.equals(that.alias, bs) case _ => false } + // equals comes from case class; no matching override is needed override def eql(that: Type): Boolean = that match { case that: TypeAlias => alias.eq(that.alias) @@ -3582,6 +3681,12 @@ object Types { derivedAnnotatedType(tpe.stripTypeVar, annot) override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { + case that: AnnotatedType => tpe.equals(that.tpe, bs) && (annot `eq` that.annot) + case _ => false + } + // equals comes from case class; no matching override is needed } object AnnotatedType { @@ -3596,7 +3701,8 @@ 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(bs: Binders) = doHash(bs, elemType) + override def stableHash = elemType.stableHash override def eql(that: Type) = that match { case that: JavaArrayType => elemType.eq(that.elemType) @@ -3614,12 +3720,12 @@ object Types { /** Sentinel for "missing type" */ @sharable case object NoType extends CachedGroundType { override def exists = false - override def computeHash = hashSeed + override def computeHash(bs: Binders) = hashSeed } /** Missing prefix */ @sharable case object NoPrefix extends CachedGroundType { - override def computeHash = hashSeed + override def computeHash(bs: Binders) = hashSeed } /** A common superclass of `ErrorType` and `TryDynamicCallSite`. Instances of this @@ -3659,12 +3765,19 @@ object Types { else if (!optBounds.exists) WildcardType else WildcardType(optBounds.asInstanceOf[TypeBounds]) - override def computeHash = doHash(optBounds) + override def computeHash(bs: Binders) = doHash(bs, optBounds) + override def stableHash = optBounds.stableHash override def eql(that: Type) = that match { case that: WildcardType => optBounds.eq(that.optBounds) case _ => false } + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: WildcardType => optBounds.equals(that.optBounds, bs) + case _ => false + } + // equals comes from case class; no matching override is needed } final class CachedWildcardType(optBounds: Type) extends WildcardType(optBounds) @@ -3795,7 +3908,7 @@ object Types { implicit val ctx = this.ctx tp match { case tp: NamedType => - if (stopAtStatic && tp.symbol.isStatic) tp + if (stopAtStatic && tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else { val prefix1 = atVariance(variance max 0)(this(tp.prefix)) // A prefix is never contravariant. Even if say `p.A` is used in a contravariant @@ -4184,7 +4297,7 @@ object Types { record(s"foldOver total") tp match { case tp: TypeRef => - if (stopAtStatic && tp.symbol.isStatic) x + if (stopAtStatic && tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) x else { val tp1 = tp.prefix.lookupRefined(tp.name) if (tp1.exists) this(x, tp1) else applyToPrefix(x, tp) @@ -4214,10 +4327,8 @@ object Types { variance = -variance this(y, tp.resultType) - case NoPrefix => x - case tp: TermRef => - if (stopAtStatic && tp.currentSymbol.isStatic) x + if (stopAtStatic && tp.currentSymbol.isStatic || (tp.prefix `eq` NoPrefix)) x else applyToPrefix(x, tp) case tp: TypeVar => @@ -4297,11 +4408,14 @@ object Types { (implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { override def stopAtStatic = false def maybeAdd(x: mutable.Set[NamedType], tp: NamedType) = if (p(tp)) x += tp else x - val seen: mutable.Set[Type] = mutable.Set() + val seen = new util.HashSet[Type](64) { + override def hash(x: Type): Int = System.identityHashCode(x) + override def isEqual(x: Type, y: Type) = x.eq(y) + } def apply(x: mutable.Set[NamedType], tp: Type): mutable.Set[NamedType] = if (seen contains tp) x else { - seen += tp + seen.addEntry(tp) tp match { case tp: TypeRef => foldOver(maybeAdd(x, tp), tp) @@ -4447,4 +4561,14 @@ object Types { // ----- Decorator implicits -------------------------------------------- implicit def decorateTypeApplications(tpe: Type): TypeApplications = new TypeApplications(tpe) + + implicit class typeListDeco(val tps1: List[Type]) extends AnyVal { + @tailrec def stableHash: Boolean = + tps1.isEmpty || tps1.head.stableHash && tps1.tail.stableHash + @tailrec def equalElements(tps2: List[Type], bs: BinderPairs): Boolean = + (tps1 `eq` tps2) || { + if (tps1.isEmpty) tps2.isEmpty + else tps2.nonEmpty && tps1.head.equals(tps2.head, bs) && tps1.tail.equalElements(tps2.tail, bs) + } + } } diff --git a/compiler/src/dotty/tools/dotc/core/Uniques.scala b/compiler/src/dotty/tools/dotc/core/Uniques.scala index 36228f418b9e..b9cc8ec486e6 100644 --- a/compiler/src/dotty/tools/dotc/core/Uniques.scala +++ b/compiler/src/dotty/tools/dotc/core/Uniques.scala @@ -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 = doHash(null, designator, prefix) if (monitored) recordCaching(h, classOf[NamedType]) def newType = if (isTerm) new CachedTermRef(prefix, designator, h) @@ -80,7 +80,7 @@ object Uniques { } def enterIfNew(tycon: Type, args: List[Type]): AppliedType = { - val h = doHash(tycon, args) + val h = doHash(null, 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 fb449febad1f..7bf765c21ae1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -186,7 +186,7 @@ object Implicits { * @param outerCtx the next outer context that makes visible further implicits */ class ContextualImplicits(val refs: List[ImplicitRef], val outerImplicits: ContextualImplicits)(initctx: Context) extends ImplicitRefs(initctx) { - private val eligibleCache = new mutable.AnyRefMap[Type, List[Candidate]] + private val eligibleCache = new java.util.IdentityHashMap[Type, List[Candidate]] /** The level increases if current context has a different owner or scope than * the context of the next-outer ImplicitRefs. This is however disabled under @@ -211,8 +211,9 @@ 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) - else eligibleCache get tp match { - case Some(eligibles) => + else { + val eligibles = eligibleCache.get(tp) + if (eligibles != null) { def elided(ci: ContextualImplicits): Int = { val n = ci.refs.length if (ci.isOuterMost) n @@ -220,18 +221,18 @@ object Implicits { } if (monitored) record(s"elided eligible refs", elided(this)) eligibles - case None => - if (ctx eq NoContext) Nil - else { - val savedEphemeral = ctx.typerState.ephemeral - ctx.typerState.ephemeral = false - try { - val result = computeEligible(tp) - if (ctx.typerState.ephemeral) record("ephemeral cache miss: eligible") - else eligibleCache(tp) = result - result - } finally ctx.typerState.ephemeral |= savedEphemeral - } + } + else if (ctx eq NoContext) Nil + else { + val savedEphemeral = ctx.typerState.ephemeral + ctx.typerState.ephemeral = false + try { + val result = computeEligible(tp) + if (ctx.typerState.ephemeral) record("ephemeral cache miss: eligible") + else eligibleCache.put(tp, result) + result + } finally ctx.typerState.ephemeral |= savedEphemeral + } } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index b975fe51c198..2bca28f56cd5 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -130,9 +130,9 @@ object ProtoTypes { override def deepenProto(implicit ctx: Context) = derivedSelectionProto(name, memberProto.deepenProto, compat) - override def computeHash = { + override def computeHash(bs: Hashable.Binders) = { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) - addDelta(doHash(name, memberProto), delta) + addDelta(doHash(bs, name, memberProto), delta) } } @@ -326,7 +326,7 @@ object ProtoTypes { } class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) { - override def computeHash = doHash(argType, resultType) + override def computeHash(bs: Hashable.Binders) = doHash(bs, argType, resultType) } object ViewProto { @@ -397,9 +397,20 @@ 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, make a copy of necessary */ + def ensureFresh(tl: TypeLambda): TypeLambda = + if (state.constraint contains tl) { + var paramInfos = tl.paramInfos + if (tl.isInstanceOf[HKLambda]) { + // HKLambdas are hash-consed, need to create an artificial difference by adding + // a LazyRef to a bound. + val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos + paramInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 + } + ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, 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) @@ -480,7 +491,7 @@ object ProtoTypes { */ final def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef])(implicit ctx: Context): Type = tp match { case tp: NamedType => // default case, inlined for speed - if (tp.symbol.isStatic) tp + if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen)) case tp @ AppliedType(tycon, args) => wildApprox(tycon, theMap, seen) match { @@ -540,7 +551,7 @@ object ProtoTypes { tp.derivedViewProto( wildApprox(tp.argType, theMap, seen), wildApprox(tp.resultType, theMap, seen)) - case _: ThisType | _: BoundType | NoPrefix => // default case, inlined for speed + case _: ThisType | _: BoundType => // default case, inlined for speed tp case _ => (if (theMap != null && seen.eq(theMap.seen)) theMap else new WildApproxMap(seen)) diff --git a/compiler/src/dotty/tools/dotc/util/HashSet.scala b/compiler/src/dotty/tools/dotc/util/HashSet.scala index 4df1568dd2ea..9ffdeac132de 100644 --- a/compiler/src/dotty/tools/dotc/util/HashSet.scala +++ b/compiler/src/dotty/tools/dotc/util/HashSet.scala @@ -9,6 +9,10 @@ class HashSet[T >: Null <: AnyRef](powerOfTwoInitialCapacity: Int, loadFactor: F protected def isEqual(x: T, y: T): Boolean = x.equals(y) + // Counters for Stats + var accesses = 0 + var misses = 0 + clear() /** The number of elements in the set */ @@ -37,10 +41,12 @@ class HashSet[T >: Null <: AnyRef](powerOfTwoInitialCapacity: Int, loadFactor: F * If not, enter `x` in set and return `x`. */ def findEntryOrUpdate(x: T): T = { + if (Stats.enabled) accesses += 1 var h = index(hash(x)) var entry = entryAt(h) while (entry ne null) { if (isEqual(x, entry)) return entry + if (Stats.enabled) misses += 1 h = index(h + 1) entry = entryAt(h) } @@ -57,9 +63,11 @@ class HashSet[T >: Null <: AnyRef](powerOfTwoInitialCapacity: Int, loadFactor: F /** The entry in the set such that `isEqual(x, entry)`, or else `null`. */ def findEntry(x: T): T = { + if (Stats.enabled) accesses += 1 var h = index(hash(x)) var entry = entryAt(h) while ((entry ne null) && !isEqual(x, entry)) { + if (Stats.enabled) misses += 1 h = index(h + 1) entry = entryAt(h) } @@ -70,10 +78,12 @@ class HashSet[T >: Null <: AnyRef](powerOfTwoInitialCapacity: Int, loadFactor: F /** Add entry `x` to set */ def addEntry(x: T): Unit = { + if (Stats.enabled) accesses += 1 var h = index(hash(x)) var entry = entryAt(h) while (entry ne null) { if (isEqual(x, entry)) return + if (Stats.enabled) misses += 1 h = index(h + 1) entry = entryAt(h) } @@ -109,10 +119,12 @@ class HashSet[T >: Null <: AnyRef](powerOfTwoInitialCapacity: Int, loadFactor: F * follow a `findEntryByhash` or `nextEntryByHash` operation. */ protected def nextEntryByHash(hashCode: Int): T = { + if (Stats.enabled) accesses += 1 var entry = table(rover) while (entry ne null) { rover = index(rover + 1) if (hash(entry.asInstanceOf[T]) == hashCode) return entry.asInstanceOf[T] + if (Stats.enabled) misses += 1 entry = table(rover) } null diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index 55684a8850bc..742ba30723c6 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -83,7 +83,7 @@ import collection.mutable hb.continue = false println() println(hits.toList.sortBy(_._2).map{ case (x, y) => s"$x -> $y" } mkString "\n") - println(s"sizes: ${ctx.base.uniquesSizes}") + println(s"uniqueInfo (size, accesses, collisions): ${ctx.base.uniquesSizes}") } } else op } 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) + } +}