From f00e8834b94a1bc6c5816f7f193e23b38c3bb552 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 12:04:41 +0100 Subject: [PATCH 01/11] 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 52ce035ed327..477975507d25 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2666,7 +2666,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._ @@ -2783,7 +2786,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 +2803,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 +2960,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 6290a65f0eac2f41a16a9309b1b2d3cf48660797 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Feb 2018 12:53:52 +0100 Subject: [PATCH 02/11] 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 477975507d25..b2bf81206e8c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1399,15 +1399,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 @@ -1445,14 +1448,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 +1462,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 +1484,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") @@ -2025,7 +2028,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 } @@ -2149,7 +2152,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 +2180,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 +2201,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 +2265,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 => @@ -2326,7 +2329,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 @@ -2432,7 +2435,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) @@ -2493,7 +2496,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) @@ -2559,7 +2562,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) @@ -2643,7 +2646,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 => @@ -3119,12 +3122,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 { @@ -3140,6 +3144,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 { @@ -3153,7 +3159,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 @@ -3210,7 +3216,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) @@ -3231,7 +3237,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 } @@ -3337,7 +3343,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 = { @@ -3412,7 +3418,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 => @@ -3495,7 +3501,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 @@ -3518,7 +3524,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 @@ -3577,7 +3583,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) @@ -3595,12 +3601,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 @@ -3640,7 +3646,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 b74245fc1ad8aa1c547f681c75a28adf50886908 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Feb 2018 12:05:42 +0100 Subject: [PATCH 03/11] 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 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/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 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 b2bf81206e8c..8a3cb0125d5c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1401,7 +1401,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 @@ -3782,7 +3782,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 @@ -4171,7 +4171,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) @@ -4201,10 +4201,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 => @@ -4284,11 +4282,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 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 c9790f78efaf..207f16cf2fab 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -480,7 +480,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 +540,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 3e15796755e2482e2cc9e9e722300e63c5ba9a93 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Feb 2018 13:30:28 +0100 Subject: [PATCH 04/11] Fix NamedPartsAccumulator --- compiler/src/dotty/tools/dotc/core/Types.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8a3cb0125d5c..c7ac9048b41d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1401,7 +1401,7 @@ object Types { /** Equality used for hash-consing; uses `eq` on all recursive invocations. */ - def eql(that: Type): Boolean = this.iso(that, null) + def eql(that: Type): Boolean = this.equals(that) /** customized hash code of this type. * NotCached for uncached types. Cached types @@ -4282,8 +4282,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] = From e1a858873fe133b91a6516ee99552b6ac133eaea Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Feb 2018 20:40:12 +0100 Subject: [PATCH 05/11] Better stats for unique tables Now shows also total umber of accesses and hash collisions for each unique types set. --- .../src/dotty/tools/dotc/core/Contexts.scala | 3 ++- .../src/dotty/tools/dotc/util/HashSet.scala | 12 ++++++++++++ .../src/dotty/tools/dotc/util/Stats.scala | 2 +- tests/pos/i3965.scala | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i3965.scala 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/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..f7acb77843a7 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"uniqieInfo (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) + } +} From ef993abd48976aca50d1da9dfd9aba8c6402c980 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Feb 2018 11:15:05 +0100 Subject: [PATCH 06/11] Structural hashing and equality for dependent types. Dependent types RecType and HKLambda were generative. Any two such types were considered to be different, even if they had the same structure. This causes problems for subtyping and constraint solving. For instance, we cannot detect that a Hk-Lambda has already been added to a constraint, which can cause a cycle. Also, monitoredIsSubtype would not work if the compared types are dependent. Test cases are i3695.scala and i3965a.scala. To fix this, we need to have a notion of hashing and equality which identifies isomorphic HkLambdas and RecTypes. Since these are very frequently called operations, a lot of attention to detail is needed in order not to lose performance. --- .../src/dotty/tools/dotc/core/Hashable.scala | 20 +- .../src/dotty/tools/dotc/core/Types.scala | 221 +++++++++++++----- .../dotty/tools/dotc/typer/ProtoTypes.scala | 15 +- tests/pos/i3965a.scala | 15 ++ 4 files changed, 207 insertions(+), 64 deletions(-) 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 cf52839bcc8f..188c9bf0cb66 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashable.scala @@ -3,10 +3,18 @@ package core import Types._ import scala.util.hashing.{ MurmurHash3 => hashing } +import annotation.tailrec object Hashable { - - type Binders = Array[BindingType] + + /** 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. @@ -36,7 +44,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)) @@ -72,6 +80,7 @@ trait Hashable { finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tps) } + protected final def doHash(x: Any): Int = finishHash(hashing.mix(hashSeed, x.hashCode), 1) @@ -93,15 +102,14 @@ 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(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) = 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 c7ac9048b41d..47a6a76e945e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1399,7 +1399,16 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) - /** Equality used for hash-consing; uses `eq` on all recursive invocations. + 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)`. + */ + 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 inloved. The latter demand a deep isomorphism check. */ def eql(that: Type): Boolean = this.equals(that) @@ -1412,6 +1421,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 ---------------------------------------------- @@ -1515,10 +1527,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 { @@ -1554,6 +1583,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 @@ -2020,16 +2050,23 @@ 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(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 } @@ -2266,6 +2303,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 => @@ -2274,6 +2312,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) @@ -2329,15 +2375,20 @@ 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 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 } @@ -2382,7 +2433,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 @@ -2418,9 +2469,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 @@ -2434,13 +2498,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 - } } final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) @@ -2469,7 +2526,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") @@ -2495,13 +2552,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 - } } final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) @@ -2563,11 +2613,17 @@ 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) 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) @@ -2646,24 +2702,23 @@ 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) + override def computeHash(bs: Binders) = + doHash(new Binders(this, bs), paramNames, resType, paramInfos) - final override def equals(that: Any) = that match { - case that: HKLambda => - paramNames == that.paramNames && - paramInfos == that.paramInfos && - resType == that.resType && - companion.eq(that.companion) - case _ => - false - } + override def stableHash = resType.stableHash && paramInfos.stableHash + + final override def equals(that: Any) = equals(that, null) - final override def eql(that: Type) = that match { + // No definition of `eql` --> fall back on equals, which calls iso + + 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 = new BinderPairs(this, that, bs) + paramInfos.equalElements(that.paramInfos, bs1) && + resType.equals(that.resType, bs1) + } case _ => false } @@ -3079,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 @@ -3124,7 +3180,18 @@ 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 { + 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) { @@ -3144,8 +3211,7 @@ 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 ??? + override def stableHash = false } abstract class ParamRef extends BoundType { @@ -3159,10 +3225,12 @@ 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 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 => binder.equalBinder(that.binder, bs) && paramNum == that.paramNum case _ => false } @@ -3218,8 +3286,10 @@ 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 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 } @@ -3419,6 +3489,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 => @@ -3430,6 +3501,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)" } @@ -3502,10 +3585,13 @@ object Types { } override def computeHash(bs: Binders) = doHash(bs, lo, hi) + override def stableHash = lo.stableHash && hi.stableHash + + override def equals(that: Any): Boolean = equals(that, null) - 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 } @@ -3525,9 +3611,10 @@ 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 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 } @@ -3569,6 +3656,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 { @@ -3584,6 +3676,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) @@ -3647,11 +3740,17 @@ 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) 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) @@ -4435,4 +4534,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/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 207f16cf2fab..9f6c10671757 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -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 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) 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 d4764c5ec4d6a5ceded8ababa7b2dcbd0cf03c52 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Feb 2018 17:50:49 +0100 Subject: [PATCH 07/11] Cache all method and polytypes --- .../src/dotty/tools/dotc/core/Types.scala | 25 ++++++++++--------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 13 ++++------ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 47a6a76e945e..9e6e83ab107d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2663,6 +2663,7 @@ object Types { final def isHigherKinded = isInstanceOf[TypeProxy] private[this] var myParamRefs: List[ParamRefType] = null + private[this] var myStableHash: Byte = 0 def paramRefs: List[ParamRefType] = { if (myParamRefs == null) myParamRefs = paramNames.indices.toList.map(newParamRef) @@ -2695,24 +2696,20 @@ object Types { x => paramInfos.mapConserve(_.subst(this, x).asInstanceOf[PInfo]), x => resType.subst(this, x)) - protected def prefixString: String - final override def toString = s"$prefixString($paramNames, $paramInfos, $resType)" - } - - abstract class HKLambda extends CachedProxyType with LambdaType { - final override def underlying(implicit ctx: Context) = resType - override def computeHash(bs: Binders) = doHash(new Binders(this, bs), paramNames, resType, paramInfos) - override def stableHash = resType.stableHash && paramInfos.stableHash + override def stableHash = { + if (myStableHash == 0) myStableHash = if (resType.stableHash && paramInfos.stableHash) 1 else -1 + myStableHash > 0 + } final override def equals(that: Any) = equals(that, null) // No definition of `eql` --> fall back on equals, which calls iso final override def iso(that: Any, bs: BinderPairs) = that match { - case that: HKLambda => + case that: LambdaType => paramNames.eqElements(that.paramNames) && companion.eq(that.companion) && { val bs1 = new BinderPairs(this, that, bs) @@ -2722,13 +2719,17 @@ object Types { case _ => false } + + protected def prefixString: String + final override def toString = s"$prefixString($paramNames, $paramInfos, $resType)" } - 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] + abstract class HKLambda extends CachedProxyType with LambdaType { + final override def underlying(implicit ctx: Context) = resType } + abstract class MethodOrPoly extends CachedGroundType with LambdaType with MethodicType + trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ type ThisName = TermName diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 9f6c10671757..82e81d44bc63 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -400,14 +400,11 @@ object ProtoTypes { /** 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)) + // Type lambdas are hash-consed, need to create an artificial difference by adding + // a LazyRef to a bound. + val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos + val newParamInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 + ensureFresh(tl.newLikeThis(tl.paramNames, newParamInfos, tl.resultType)) } else tl val added = ensureFresh(tl) From 0c63930551fa58c27bc7bf8876d608a8831ec880 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Feb 2018 22:55:32 +0100 Subject: [PATCH 08/11] Revert: Cache all method and polytypes (reverted from commit c0e7113c2f7f5c528f4161e47b33c07d4f5461bd) --- .../src/dotty/tools/dotc/core/Types.scala | 25 +++++++++---------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 13 ++++++---- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9e6e83ab107d..47a6a76e945e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2663,7 +2663,6 @@ object Types { final def isHigherKinded = isInstanceOf[TypeProxy] private[this] var myParamRefs: List[ParamRefType] = null - private[this] var myStableHash: Byte = 0 def paramRefs: List[ParamRefType] = { if (myParamRefs == null) myParamRefs = paramNames.indices.toList.map(newParamRef) @@ -2696,20 +2695,24 @@ object Types { x => paramInfos.mapConserve(_.subst(this, x).asInstanceOf[PInfo]), x => resType.subst(this, x)) + protected def prefixString: String + final override def toString = s"$prefixString($paramNames, $paramInfos, $resType)" + } + + abstract class HKLambda extends CachedProxyType with LambdaType { + final override def underlying(implicit ctx: Context) = resType + override def computeHash(bs: Binders) = doHash(new Binders(this, bs), paramNames, resType, paramInfos) - override def stableHash = { - if (myStableHash == 0) myStableHash = if (resType.stableHash && paramInfos.stableHash) 1 else -1 - myStableHash > 0 - } + 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 iso(that: Any, bs: BinderPairs) = that match { - case that: LambdaType => + case that: HKLambda => paramNames.eqElements(that.paramNames) && companion.eq(that.companion) && { val bs1 = new BinderPairs(this, that, bs) @@ -2719,17 +2722,13 @@ object Types { case _ => false } - - protected def prefixString: String - final override def toString = s"$prefixString($paramNames, $paramInfos, $resType)" } - abstract class HKLambda extends CachedProxyType with LambdaType { - final override def underlying(implicit ctx: Context) = resType + 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] } - abstract class MethodOrPoly extends CachedGroundType with LambdaType with MethodicType - trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ type ThisName = TermName diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 82e81d44bc63..9f6c10671757 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -400,11 +400,14 @@ object ProtoTypes { /** Ensure that `tl` is not already in constraint, make a copy of necessary */ def ensureFresh(tl: TypeLambda): TypeLambda = if (state.constraint contains tl) { - // Type lambdas are hash-consed, need to create an artificial difference by adding - // a LazyRef to a bound. - val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos - val newParamInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 - ensureFresh(tl.newLikeThis(tl.paramNames, newParamInfos, tl.resultType)) + 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) From cdfd219c7c783ab8ef300be3c6f7616200a745fb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Feb 2018 18:00:08 +0100 Subject: [PATCH 09/11] Fix typo in diagnostics --- compiler/src/dotty/tools/dotc/util/Stats.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index f7acb77843a7..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"uniqieInfo (size, accesses, collisions): ${ctx.base.uniquesSizes}") + println(s"uniqueInfo (size, accesses, collisions): ${ctx.base.uniquesSizes}") } } else op } From 7cd930b3e21be07aa78d4516a313b028736a2937 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Feb 2018 16:38:38 +0100 Subject: [PATCH 10/11] Address reviewers comments --- .../src/dotty/tools/dotc/core/Types.scala | 36 ++++++++++++++++--- .../dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 47a6a76e945e..527d0f32cfa8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1399,16 +1399,21 @@ 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, using comparer `e`? + /** 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 inloved. The latter demand a deep isomorphism check. + * except where a BindingType is involved. The latter demand a deep isomorphism check. */ def eql(that: Type): Boolean = this.equals(that) @@ -1536,7 +1541,7 @@ object Types { * Otherise the standard identity hash. */ override def identityHash(bs: Binders) = { - def recur(n: Int, tp: BindingType, rest: Binders): Int = + 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) @@ -2320,6 +2325,7 @@ object Types { 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) @@ -2482,6 +2488,7 @@ object Types { 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 AndOrType { @@ -2624,6 +2631,7 @@ object Types { 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) @@ -2727,6 +2735,22 @@ 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] + + final override def equals(that: Any) = equals(that, null) + + // No definition of `eql` --> fall back on equals, which calls iso + + 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 TermLambda extends LambdaType { thisLambdaType => @@ -3192,6 +3216,7 @@ object Types { 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) { @@ -3230,7 +3255,7 @@ object Types { override def equals(that: Any) = equals(that, null) override def iso(that: Any, bs: BinderPairs) = that match { - case that: ParamRef => binder.equalBinder(that.binder, bs) && paramNum == that.paramNum + case that: ParamRef => paramNum == that.paramNum && binder.equalBinder(that.binder, bs) case _ => false } @@ -3617,6 +3642,7 @@ object Types { 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) @@ -3661,6 +3687,7 @@ object Types { 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 { @@ -3751,6 +3778,7 @@ object Types { 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) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 9f6c10671757..2bca28f56cd5 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -402,7 +402,7 @@ object ProtoTypes { 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 + // 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 From 15644d68057de20490988ab7c1f075a323bfff08 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Feb 2018 18:34:02 +0100 Subject: [PATCH 11/11] Fix MethodOrPoly We need to add structural `equals` since MethodOrPoly's can be part of RefinedTypes. --- compiler/src/dotty/tools/dotc/core/Types.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 527d0f32cfa8..b6c6b1e79209 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2734,11 +2734,10 @@ 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] final override def equals(that: Any) = equals(that, null) - // No definition of `eql` --> fall back on equals, which calls iso + // No definition of `eql` --> fall back on equals, which is `eq` final override def iso(that: Any, bs: BinderPairs) = that match { case that: MethodOrPoly =>