From 2dbf4134e5b234a9232a469c54432a0a5ef3cd24 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 12:04:41 +0100 Subject: [PATCH 1/8] Make MethodTypes uncached Since correct hashing under binders seems to be very expensive (see performance data for #3970), let's try have fewer types that require this. --- .../src/dotty/tools/dotc/core/Types.scala | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e7f3acf4d033..9c9837ac5447 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2657,7 +2657,10 @@ object Types { } } - trait MethodOrPoly extends LambdaType with MethodicType + abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType { + final override def hashCode = System.identityHashCode(this) + final override def equals(other: Any) = this `eq` other.asInstanceOf[AnyRef] + } trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ @@ -2774,7 +2777,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,28 +2794,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 +2951,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 From a87540a49e8339ae666d22698d58848f5919b82f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 12:53:52 +0100 Subject: [PATCH 2/8] Add binders parameter to all hashcode computations --- .../src/dotty/tools/dotc/core/Hashable.scala | 53 ++++++++------- .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 66 ++++++++++--------- .../src/dotty/tools/dotc/core/Uniques.scala | 4 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 6 +- 5 files changed, 71 insertions(+), 60 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index e4510c53e6c1..cf52839bcc8f 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -6,6 +6,8 @@ import scala.util.hashing.{ MurmurHash3 => hashing } object Hashable { + type Binders = Array[BindingType] + /** A hash value indicating that the underlying type is not * cached in uniques. */ @@ -33,26 +35,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.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,35 +66,35 @@ 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(bs: Binders, tp: Type): Int = + finishHash(bs, 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, x1: Any, tp2: Type): Int = + finishHash(bs, 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(bs: Binders, tp1: Type, tp2: Type): Int = + finishHash(bs, 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(bs: Binders, x1: Any, tp2: Type, tp3: Type): Int = + finishHash(bs, 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(bs: Binders, tp1: Type, tps2: List[Type]): Int = + finishHash(bs, 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(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 = + protected final def doHash(bs: Binders, x1: Int, x2: Int): Int = finishHash(hashing.mix(hashing.mix(hashSeed, x1), x2), 1) protected final def addDelta(elemHash: Int, delta: Int) = diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 05dc572dc51e..35cd043a1232 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) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9c9837ac5447..f6fdd0ab034f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1394,15 +1394,18 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) + /** 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 hashcode relative to enclosing binders `bs` */ + def computeHash(bs: Binders): Int } // end Type @@ -1440,14 +1443,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. */ @@ -1455,19 +1457,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") @@ -1477,6 +1479,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") @@ -2016,7 +2019,7 @@ object Types { false } - override def computeHash = unsupported("computeHash") + override def computeHash(bs: Binders) = doHash(bs, designator, prefix) override def eql(that: Type) = this eq that // safe because named types are hash-consed separately } @@ -2140,7 +2143,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) @@ -2168,7 +2171,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) @@ -2189,7 +2192,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) @@ -2253,7 +2256,7 @@ 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 eql(that: Type) = that match { case that: RefinedType => @@ -2317,7 +2320,7 @@ object Types { refacc.apply(false, tp) } - override def computeHash = doHash(parent) + override def computeHash(bs: Binders) = doHash(bs, parent) override def equals(that: Any) = that match { case that: RecType => parent == that.parent @@ -2423,7 +2426,7 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedAndType(tp1, tp2) - override def computeHash = doHash(tp1, tp2) + override def computeHash(bs: Binders) = doHash(bs, tp1, tp2) override def eql(that: Type) = that match { case that: AndType => tp1.eq(that.tp1) && tp2.eq(that.tp2) @@ -2484,7 +2487,7 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedOrType(tp1, tp2) - override def computeHash = doHash(tp1, tp2) + override def computeHash(bs: Binders) = doHash(bs, tp1, tp2) override def eql(that: Type) = that match { case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) @@ -2550,7 +2553,7 @@ 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 eql(that: Type) = that match { case that: ExprType => resType.eq(that.resType) @@ -2634,7 +2637,7 @@ 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) + final override def computeHash(bs: Binders) = doHash(bs, paramNames, resType, paramInfos) final override def equals(that: Any) = that match { case that: HKLambda => @@ -3110,12 +3113,13 @@ 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 eql(that: Type) = this `eq` that // safe because applied types are hash-consed separately } 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 { @@ -3131,6 +3135,8 @@ object Types { type BT <: Type val binder: BT def copyBoundType(bt: BT): Type + override def identityHash(bs: Binders) = + if (bs == null) super.identityHash(bs) else ??? } abstract class ParamRef extends BoundType { @@ -3144,7 +3150,7 @@ object Types { else infos(paramNum) } - override def computeHash = doHash(paramNum, binder.identityHash) + override def computeHash(bs: Binders) = doHash(bs, paramNum, binder.identityHash(bs)) override def equals(that: Any) = that match { case that: ParamRef => binder.eq(that.binder) && paramNum == that.paramNum @@ -3201,7 +3207,7 @@ 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) @@ -3222,7 +3228,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 } @@ -3328,7 +3334,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 = { @@ -3403,7 +3409,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(bs: Binders) = doHash(bs, cls, prefix) override def eql(that: Type) = that match { case that: ClassInfo => @@ -3486,7 +3492,7 @@ object Types { case _ => super.| (that) } - override def computeHash = doHash(lo, hi) + override def computeHash(bs: Binders) = doHash(bs, lo, hi) override def equals(that: Any): Boolean = that match { case that: TypeAlias => false @@ -3509,7 +3515,7 @@ 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 equals(that: Any): Boolean = that match { case that: TypeAlias => alias == that.alias @@ -3568,7 +3574,7 @@ 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 eql(that: Type) = that match { case that: JavaArrayType => elemType.eq(that.elemType) @@ -3586,12 +3592,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 @@ -3631,7 +3637,7 @@ 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 eql(that: Type) = that match { case that: WildcardType => optBounds.eq(that.optBounds) 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/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index b975fe51c198..c9790f78efaf 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 { From 232f65a626039dfe82ea46498748d28c0fda2a01 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 17:50:47 +0100 Subject: [PATCH 3/8] Add BinderPairs parameter when testing equality --- .../src/dotty/tools/dotc/core/Hashable.scala | 3 +- .../src/dotty/tools/dotc/core/Types.scala | 153 ++++++++++++++---- .../dotty/tools/dotc/typer/ProtoTypes.scala | 22 ++- 3 files changed, 142 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index cf52839bcc8f..5cfc5aea3276 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -6,7 +6,8 @@ import scala.util.hashing.{ MurmurHash3 => hashing } object Hashable { - type Binders = Array[BindingType] + class Binders(tp: BindingType, next: Binders) + class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs) /** A hash value indicating that the underlying type is not * cached in uniques. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f6fdd0ab034f..b8e4a9120b37 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1394,6 +1394,16 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) + final override def equals(that: Any) = this.equals(that, null) + + final def equals(that: Any, bs: BinderPairs): Boolean = + (this `eq` that.asInstanceOf[AnyRef]) || this.iso(that, bs) + + /** Is `this` isomorphic to that, using comparer `e`? + * It is assumed that `this ne that`. + */ + def iso(that: Any, bs: BinderPairs): Boolean = false + /** Equality used for hash-consing; uses `eq` on all recursive invocations. */ def eql(that: Type): Boolean = this.equals(that) @@ -1513,7 +1523,14 @@ object Types { /** A marker trait for types that bind other types that refer to them. * Instances are: LambdaType, RecType. */ - trait BindingType extends Type + trait BindingType extends Type { + + override def identityHash(bs: Binders) = + if (bs == null) super.identityHash(bs) else ??? + + def equalBinder(that: BindingType, bs: BinderPairs): Boolean = + (this `eq` that) /* || ??? */ + } /** A trait for proto-types, used as expected types in typer */ trait ProtoType extends Type { @@ -2011,10 +2028,10 @@ object Types { } } - override def equals(that: Any) = that match { + override def iso(that: Any, bs: BinderPairs): Boolean = that match { case that: NamedType => - this.designator == that.designator && - this.prefix == that.prefix + designator == that.designator && + prefix.equals(that.prefix, bs) case _ => false } @@ -2149,6 +2166,11 @@ object Types { case that: ThisType => tref.eq(that.tref) case _ => false } + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { + case that: ThisType => tref.equals(that.tref, bs) + case _ => false + } } final class CachedThisType(tref: TypeRef) extends ThisType(tref) @@ -2177,6 +2199,12 @@ object Types { case that: SuperType => thistpe.eq(that.thistpe) && supertpe.eq(that.supertpe) case _ => false } + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { + case that: SuperType => + thistpe.equals(that.thistpe, bs) && supertpe.equals(that.supertpe, bs) + case _ => false + } } final class CachedSuperType(thistpe: Type, supertpe: Type) extends SuperType(thistpe, supertpe) @@ -2193,6 +2221,11 @@ object Types { override def underlying(implicit ctx: Context) = value.tpe override def computeHash(bs: Binders) = doHash(value) + + override def iso(that: Any, bs: BinderPairs): Boolean = that match { + case that: ConstantType => value.equals(that.value) + case _ => false + } } final class CachedConstantType(value: Constant) extends ConstantType(value) @@ -2219,7 +2252,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) } @@ -2265,6 +2297,14 @@ 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 + } } class CachedRefinedType(parent: Type, refinedName: Name, refinedInfo: Type) @@ -2322,13 +2362,14 @@ object Types { override def computeHash(bs: Binders) = doHash(bs, parent) - override def equals(that: Any) = that match { - case that: RecType => parent == that.parent + override def eql(that: Type) = that match { + case that: RecType => parent.eq(that.parent) case _ => false } - override def eql(that: Type) = that match { - case that: RecType => parent.eq(that.parent) + override def iso(that: Any, bs: BinderPairs) = that match { + case that: RecType => + parent.equals(that.parent, bs/*!!!*/) case _ => false } @@ -2432,6 +2473,11 @@ object Types { case that: AndType => tp1.eq(that.tp1) && tp2.eq(that.tp2) case _ => false } + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: AndType => tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs) + case _ => false + } } final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) @@ -2493,6 +2539,11 @@ object Types { case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) case _ => false } + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: OrType => tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs) + case _ => false + } } final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) @@ -2559,6 +2610,11 @@ object Types { 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 + } } final class CachedExprType(resultType: Type) extends ExprType(resultType) @@ -2639,22 +2695,24 @@ object Types { final override def computeHash(bs: Binders) = doHash(bs, paramNames, resType, paramInfos) - final override def equals(that: Any) = that match { + final override def eql(that: Type) = that match { case that: HKLambda => - paramNames == that.paramNames && - paramInfos == that.paramInfos && - resType == that.resType && + paramNames.equals(that.paramNames) && + paramInfos.equals(that.paramInfos) && + resType.equals(that.resType) && companion.eq(that.companion) case _ => false } - final override def eql(that: Type) = that match { + final override def iso(that: Any, bs: BinderPairs) = that match { case that: HKLambda => - paramNames.equals(that.paramNames) && - paramInfos.equals(that.paramInfos) && - resType.equals(that.resType) && - companion.eq(that.companion) + paramNames.eqElements(that.paramNames) && + companion.eq(that.companion) && { + val bs1 = bs + paramInfos.equalElements(that.paramInfos, bs1) && + resType.equals(that.resType, bs1) + } case _ => false } @@ -2662,7 +2720,6 @@ object Types { abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType { final override def hashCode = System.identityHashCode(this) - final override def equals(other: Any) = this `eq` other.asInstanceOf[AnyRef] } trait TermLambda extends LambdaType { thisLambdaType => @@ -3116,6 +3173,11 @@ object Types { override def computeHash(bs: Binders) = doHash(bs, tycon, args) 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 + } } final class CachedAppliedType(tycon: Type, args: List[Type], hc: Int) extends AppliedType(tycon, args) { @@ -3135,8 +3197,6 @@ object Types { type BT <: Type val binder: BT def copyBoundType(bt: BT): Type - override def identityHash(bs: Binders) = - if (bs == null) super.identityHash(bs) else ??? } abstract class ParamRef extends BoundType { @@ -3152,8 +3212,8 @@ object Types { override def computeHash(bs: Binders) = doHash(bs, paramNum, binder.identityHash(bs)) - 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 => binder.equalBinder(that.binder, bs) && paramNum == that.paramNum case _ => false } @@ -3209,8 +3269,8 @@ object Types { // between RecTypes and RecRefs. 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 iso(that: Any, bs: BinderPairs) = that match { + case that: RecThis => binder.equalBinder(that.binder, bs) case _ => false } @@ -3229,7 +3289,6 @@ object Types { def derivedSkolemType(info: Type)(implicit ctx: Context) = if (info eq this.info) this else SkolemType(info) 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 } @@ -3335,7 +3394,6 @@ object Types { } override def computeHash(bs: Binders): Int = identityHash(bs) - override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) override def toString = { def instStr = if (inst.exists) s" -> $inst" else "" @@ -3421,6 +3479,16 @@ object Types { case _ => false } + 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)" } @@ -3494,9 +3562,9 @@ object Types { override def computeHash(bs: Binders) = doHash(bs, lo, hi) - override def equals(that: Any): Boolean = that match { + 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 } @@ -3517,8 +3585,8 @@ object Types { override def computeHash(bs: Binders) = doHash(bs, alias) - 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 } @@ -3560,6 +3628,11 @@ 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 + } } object AnnotatedType { @@ -3580,6 +3653,11 @@ object Types { case that: JavaArrayType => elemType.eq(that.elemType) case _ => false } + + override def iso(that: Any, bs: BinderPairs) = that match { + case that: JavaArrayType => elemType.equals(that.elemType, bs) + case _ => false + } } final class CachedJavaArrayType(elemType: Type) extends JavaArrayType(elemType) object JavaArrayType { @@ -3643,6 +3721,11 @@ object Types { 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 + } } final class CachedWildcardType(optBounds: Type) extends WildcardType(optBounds) @@ -4425,4 +4508,12 @@ object Types { // ----- Decorator implicits -------------------------------------------- implicit def decorateTypeApplications(tpe: Type): TypeApplications = new TypeApplications(tpe) + + implicit class listEquals(val tps1: List[Type]) extends AnyVal { + @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/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index c9790f78efaf..eeafdd34fe64 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, bs: Hashable.BinderPairs): Boolean = that match { case that: SelectionProto => - (name eq that.name) && (memberProto == that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) + (name `eq` that.name) && + memberProto.equals(that.memberProto, bs) && + (compat `eq` that.compat) && + (privateOK == that.privateOK) case _ => false } @@ -397,9 +400,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 care 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) From dc7b25c99ce6f4a8f24f7329a7897d0d3838cd8b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 18:52:29 +0100 Subject: [PATCH 4/8] Fix hashing and equality for dependent types Rework hashing and equality so that two isomorphic types are identified even if they are dependent (i.e. have back edges from a BoundType such as ParamRef or RecThis to its HKLambda or RecType Method and PolyTypes are still generative. This fixes #3965 --- .../src/dotty/tools/dotc/core/Hashable.scala | 12 +++++++++--- .../src/dotty/tools/dotc/core/Types.scala | 15 ++++++++------- tests/pos/i3965.scala | 19 +++++++++++++++++++ tests/pos/i3965a.scala | 15 +++++++++++++++ 4 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 tests/pos/i3965.scala create mode 100644 tests/pos/i3965a.scala diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index 5cfc5aea3276..5c314995ee5c 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -3,11 +3,17 @@ package core import Types._ import scala.util.hashing.{ MurmurHash3 => hashing } +import annotation.tailrec object Hashable { - class Binders(tp: BindingType, next: Binders) - class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs) + class Binders(tp: BindingType, next: Binders) { + val hash: Int = if (next == null) 31 else next.hash * 41 + 31 + } + 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. @@ -95,7 +101,7 @@ trait Hashable { finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3) - protected final def doHash(bs: Binders, x1: Int, x2: Int): Int = + 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) = diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b8e4a9120b37..b21824c8db9e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1526,10 +1526,10 @@ object Types { trait BindingType extends Type { override def identityHash(bs: Binders) = - if (bs == null) super.identityHash(bs) else ??? + if (bs == null) super.identityHash(bs) else bs.hash def equalBinder(that: BindingType, bs: BinderPairs): Boolean = - (this `eq` that) /* || ??? */ + (this `eq` that) || bs != null && bs.matches(this, that) } /** A trait for proto-types, used as expected types in typer */ @@ -2360,7 +2360,7 @@ object Types { refacc.apply(false, tp) } - override def computeHash(bs: Binders) = doHash(bs, parent) + override def computeHash(bs: Binders) = doHash(new Binders(this, bs), parent) override def eql(that: Type) = that match { case that: RecType => parent.eq(that.parent) @@ -2369,7 +2369,7 @@ object Types { override def iso(that: Any, bs: BinderPairs) = that match { case that: RecType => - parent.equals(that.parent, bs/*!!!*/) + parent.equals(that.parent, new BinderPairs(this, that, bs)) case _ => false } @@ -2693,7 +2693,8 @@ object Types { abstract class HKLambda extends CachedProxyType with LambdaType { final override def underlying(implicit ctx: Context) = resType - final override def computeHash(bs: Binders) = doHash(bs, paramNames, resType, paramInfos) + final override def computeHash(bs: Binders) = + doHash(new Binders(this, bs), paramNames, resType, paramInfos) final override def eql(that: Type) = that match { case that: HKLambda => @@ -2709,7 +2710,7 @@ object Types { case that: HKLambda => paramNames.eqElements(that.paramNames) && companion.eq(that.companion) && { - val bs1 = bs + val bs1 = new BinderPairs(this, that, bs) paramInfos.equalElements(that.paramInfos, bs1) && resType.equals(that.resType, bs1) } @@ -3210,7 +3211,7 @@ object Types { else infos(paramNum) } - override def computeHash(bs: Binders) = doHash(bs, paramNum, binder.identityHash(bs)) + override def computeHash(bs: Binders) = doHash(paramNum, binder.identityHash(bs)) override def iso(that: Any, bs: BinderPairs) = that match { case that: ParamRef => binder.equalBinder(that.binder, bs) && paramNum == that.paramNum 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) + } +} From dab5e516f62bd9e3d6723853b3ece109057a3666 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 20:28:44 +0100 Subject: [PATCH 5/8] Compute fewer hashes Before hashing with a non-null binders list, check whether the type has a stable hash. Hash-stability results are cached in NamedTypes and AppliedTypes. --- .../src/dotty/tools/dotc/core/Hashable.scala | 3 +- .../src/dotty/tools/dotc/core/Types.scala | 81 ++++++++++++------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 2 + 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index 5c314995ee5c..b81a244e1344 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -43,7 +43,7 @@ trait Hashable { avoidSpecialHashes(hashing.finalizeHash(hashCode, arity)) final def typeHash(bs: Binders, tp: Type) = - if (bs == null) tp.hash else tp.computeHash(bs) + if (bs == null || tp.stableHash) tp.hash else tp.computeHash(bs) def identityHash(bs: Binders) = avoidSpecialHashes(System.identityHashCode(this)) @@ -100,7 +100,6 @@ trait Hashable { 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) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b21824c8db9e..1054d85619aa 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1417,6 +1417,9 @@ object Types { /** 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 // ----- Type categories ---------------------------------------------- @@ -1566,6 +1569,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 @@ -2038,6 +2042,11 @@ object Types { 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 } @@ -2289,6 +2298,7 @@ object Types { else 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 => @@ -2362,6 +2372,11 @@ object Types { override def computeHash(bs: Binders) = doHash(new Binders(this, bs), parent) + 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) case _ => false @@ -2414,7 +2429,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 @@ -2450,9 +2465,22 @@ 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 + } } - 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 @@ -2466,18 +2494,6 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedAndType(tp1, tp2) - - override def computeHash(bs: Binders) = doHash(bs, tp1, tp2) - - override def eql(that: Type) = that match { - case that: AndType => tp1.eq(that.tp1) && tp2.eq(that.tp2) - case _ => false - } - - override def iso(that: Any, bs: BinderPairs) = that match { - case that: AndType => tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs) - case _ => false - } } final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) @@ -2506,7 +2522,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") @@ -2532,18 +2548,6 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedOrType(tp1, tp2) - - override def computeHash(bs: Binders) = doHash(bs, tp1, tp2) - - override def eql(that: Type) = that match { - case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) - case _ => false - } - - override def iso(that: Any, bs: BinderPairs) = that match { - case that: OrType => tp1.equals(that.tp1, bs) && tp2.equals(that.tp2, bs) - case _ => false - } } final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) @@ -2605,6 +2609,7 @@ object Types { if (resType eq this.resType) this else ExprType(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) @@ -2693,10 +2698,11 @@ object Types { abstract class HKLambda extends CachedProxyType with LambdaType { final override def underlying(implicit ctx: Context) = resType - final override def computeHash(bs: Binders) = + override def computeHash(bs: Binders) = doHash(new Binders(this, bs), paramNames, resType, paramInfos) + override def stableHash = resType.stableHash && paramInfos.stableHash - final override def eql(that: Type) = that match { + override def eql(that: Type) = that match { case that: HKLambda => paramNames.equals(that.paramNames) && paramInfos.equals(that.paramInfos) && @@ -3128,6 +3134,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 @@ -3173,6 +3180,12 @@ object Types { 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 { @@ -3198,6 +3211,7 @@ object Types { type BT <: Type val binder: BT def copyBoundType(bt: BT): Type + override def stableHash = false } abstract class ParamRef extends BoundType { @@ -3469,6 +3483,7 @@ object Types { else ClassInfo(prefix, cls, classParents, decls, selfInfo) 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 => @@ -3562,6 +3577,7 @@ object Types { } override def computeHash(bs: Binders) = doHash(bs, lo, hi) + override def stableHash = lo.stableHash && hi.stableHash override def iso(that: Any, bs: BinderPairs): Boolean = that match { case that: TypeAlias => false @@ -3585,6 +3601,7 @@ object Types { if (alias eq this.alias) this else TypeAlias(alias) override def computeHash(bs: Binders) = doHash(bs, alias) + override def stableHash = alias.stableHash override def iso(that: Any, bs: BinderPairs): Boolean = that match { case that: TypeAlias => alias.equals(that.alias, bs) @@ -3649,6 +3666,7 @@ object Types { if (elemtp eq this.elemType) this else JavaArrayType(elemtp) 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) @@ -3717,6 +3735,7 @@ object Types { else WildcardType(optBounds.asInstanceOf[TypeBounds]) 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) @@ -4510,7 +4529,9 @@ object Types { implicit def decorateTypeApplications(tpe: Type): TypeApplications = new TypeApplications(tpe) - implicit class listEquals(val tps1: List[Type]) extends AnyVal { + 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 diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index eeafdd34fe64..21ee012c402c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -137,6 +137,7 @@ object ProtoTypes { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) addDelta(doHash(bs, name, memberProto), delta) } + override def stableHash = memberProto.stableHash } class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean) @@ -330,6 +331,7 @@ object ProtoTypes { class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) { override def computeHash(bs: Hashable.Binders) = doHash(bs, argType, resultType) + override def stableHash = argType.stableHash && resultType.stableHash } object ViewProto { From 181490a05bc4a621700d2a8627af3f3cf9701db5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 23:09:11 +0100 Subject: [PATCH 6/8] Fix identityHash for BindingTypes --- compiler/src/dotty/tools/dotc/core/Hashable.scala | 7 +++---- compiler/src/dotty/tools/dotc/core/Types.scala | 11 +++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashable.scala index b81a244e1344..3c33c984dcec 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -7,9 +7,8 @@ import annotation.tailrec object Hashable { - class Binders(tp: BindingType, next: Binders) { - val hash: Int = if (next == null) 31 else next.hash * 41 + 31 - } + class Binders(val tp: BindingType, val next: Binders) + 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) @@ -107,7 +106,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/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1054d85619aa..8cc4c4dbcc2a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1528,8 +1528,15 @@ object Types { */ trait BindingType extends Type { - override def identityHash(bs: Binders) = - if (bs == null) super.identityHash(bs) else bs.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) From 87742dc800bfcb40e2d8c3630c4825e883bfeddd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Feb 2018 12:05:42 +0100 Subject: [PATCH 7/8] Reduce number of calls to Type#equals When compiling dotc/typer/*.scala we observe a reduction of the number of calls from ~3.7m to ~1.0m. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 6 +-- .../dotty/tools/dotc/core/Substituters.scala | 40 +++++++++---------- .../tools/dotc/core/SymDenotations.scala | 6 +-- .../dotty/tools/dotc/core/TypeErasure.scala | 4 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 8 ++-- .../src/dotty/tools/dotc/core/Types.scala | 17 ++++---- .../dotty/tools/dotc/typer/Implicits.scala | 31 +++++++------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- 8 files changed, 59 insertions(+), 57 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 6dc1bedd4190..480338448740 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/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 40d2ade4e55a..3499c51706c5 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 } @@ -1281,7 +1281,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 @@ -1705,7 +1705,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 35cd043a1232..17e6108416fb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -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 8cc4c4dbcc2a..e6385477cc3f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1406,7 +1406,7 @@ object Types { /** Equality used for hash-consing; uses `eq` on all recursive invocations. */ - def eql(that: Type): Boolean = this.equals(that) + def eql(that: Type): Boolean = this.iso(that, null) /** customized hash code of this type. * NotCached for uncached types. Cached types @@ -3883,7 +3883,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 @@ -4272,7 +4272,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) @@ -4302,10 +4302,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 => @@ -4385,11 +4383,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](7) { + override def hash(x: Type): Int = x.hash + 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) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index bceb0c447067..25fa8e0a153e 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 21ee012c402c..7fa4ad39de93 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -496,7 +496,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 { @@ -556,7 +556,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)) From d9a9e500ff6443a8aa65533d48f2f9e5aec4265f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Feb 2018 13:30:28 +0100 Subject: [PATCH 8/8] Fix NamedPartsAccumulator --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e6385477cc3f..2622afaedae2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4383,8 +4383,8 @@ 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 = new util.HashSet[Type](7) { - override def hash(x: Type): Int = x.hash + 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] =