From fb3aeda647991a232bbd15685d3969e7fc3cbc8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Feb 2018 21:00:40 +0100 Subject: [PATCH 1/8] Move hashing to separate object This is a first step of a larger refactoring, where hashing and equality need to have algorithms that detect that isomorphic BindingTypes are equal. Without this, there is no good way to deal with recursive higher-kinded types, as we cannot detect that a type has already been added to a constraint or that a subtype test was already performed. --- .../core/{Hashable.scala => Hashing.scala} | 98 +++++++++---------- .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 78 +++++++-------- .../src/dotty/tools/dotc/core/Uniques.scala | 10 +- .../dotty/tools/dotc/typer/Implicits.scala | 5 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 6 +- compiler/test/dotc/tests.scala | 2 +- 7 files changed, 100 insertions(+), 101 deletions(-) rename compiler/src/dotty/tools/dotc/core/{Hashable.scala => Hashing.scala} (56%) diff --git a/compiler/src/dotty/tools/dotc/core/Hashable.scala b/compiler/src/dotty/tools/dotc/core/Hashing.scala similarity index 56% rename from compiler/src/dotty/tools/dotc/core/Hashable.scala rename to compiler/src/dotty/tools/dotc/core/Hashing.scala index e4510c53e6c1..3959a3646fb8 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashable.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashing.scala @@ -4,45 +4,24 @@ package core import Types._ import scala.util.hashing.{ MurmurHash3 => hashing } -object Hashable { +abstract class Hashing { + import Hashing._ - /** A hash value indicating that the underlying type is not - * cached in uniques. - */ - final val NotCached = 0 - - /** An alternative value returned from `hash` if the - * computed hashCode would be `NotCached`. - */ - private[core] final val NotCachedAlt = Int.MinValue - - /** A value that indicates that the hash code is unknown - */ - private[core] final val HashUnknown = 1234 - - /** An alternative value if computeHash would otherwise yield HashUnknown - */ - private[core] final val HashUnknownAlt = 4321 -} - -trait Hashable { - import Hashable._ - - protected def hashSeed: Int = getClass.hashCode - - protected final def finishHash(hashCode: Int, arity: Int): Int = + final def finishHash(hashCode: Int, arity: Int): Int = avoidSpecialHashes(hashing.finalizeHash(hashCode, arity)) - final def identityHash = avoidSpecialHashes(System.identityHashCode(this)) + protected def typeHash(tp: Type) = tp.hash + + final def identityHash(tp: Type) = avoidSpecialHashes(System.identityHashCode(tp)) protected def finishHash(seed: Int, arity: Int, tp: Type): Int = { - val elemHash = tp.hash + val elemHash = typeHash(tp) if (elemHash == NotCached) return NotCached finishHash(hashing.mix(seed, elemHash), arity + 1) } protected def finishHash(seed: Int, arity: Int, tp1: Type, tp2: Type): Int = { - val elemHash = tp1.hash + val elemHash = typeHash(tp1) if (elemHash == NotCached) return NotCached finishHash(hashing.mix(seed, elemHash), arity + 1, tp2) } @@ -52,7 +31,7 @@ trait Hashable { var xs = tps var len = arity while (xs.nonEmpty) { - val elemHash = xs.head.hash + val elemHash = typeHash(xs.head) if (elemHash == NotCached) return NotCached h = hashing.mix(h, elemHash) xs = xs.tail @@ -62,37 +41,36 @@ trait Hashable { } protected def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = { - val elemHash = tp.hash + val elemHash = typeHash(tp) if (elemHash == NotCached) return NotCached finishHash(hashing.mix(seed, elemHash), arity + 1, tps) } - protected final def doHash(x: Any): Int = - finishHash(hashing.mix(hashSeed, x.hashCode), 1) + final def doHash(clazz: Class[_], x: Any): Int = + finishHash(hashing.mix(clazz.hashCode, x.hashCode), 1) - protected final def doHash(tp: Type): Int = - finishHash(hashSeed, 0, tp) + final def doHash(clazz: Class[_], tp: Type): Int = + finishHash(clazz.hashCode, 0, tp) - protected final def doHash(x1: Any, tp2: Type): Int = - finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2) + final def doHash(clazz: Class[_], x1: Any, tp2: Type): Int = + finishHash(hashing.mix(clazz.hashCode, x1.hashCode), 1, tp2) - protected final def doHash(tp1: Type, tp2: Type): Int = - finishHash(hashSeed, 0, tp1, tp2) + final def doHash(clazz: Class[_], tp1: Type, tp2: Type): Int = + finishHash(clazz.hashCode, 0, tp1, tp2) - protected final def doHash(x1: Any, tp2: Type, tp3: Type): Int = - finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3) + final def doHash(clazz: Class[_], x1: Any, tp2: Type, tp3: Type): Int = + finishHash(hashing.mix(clazz.hashCode, x1.hashCode), 1, tp2, tp3) - protected final def doHash(tp1: Type, tps2: List[Type]): Int = - finishHash(hashSeed, 0, tp1, tps2) + final def doHash(clazz: Class[_], tp1: Type, tps2: List[Type]): Int = + finishHash(clazz.hashCode, 0, tp1, tps2) - protected final def doHash(x1: Any, tp2: Type, tps3: List[Type]): Int = - finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3) + final def doHash(clazz: Class[_], x1: Any, tp2: Type, tps3: List[Type]): Int = + finishHash(hashing.mix(clazz.hashCode, x1.hashCode), 1, tp2, tps3) + final def doHash(clazz: Class[_], x1: Int, x2: Int): Int = + finishHash(hashing.mix(hashing.mix(clazz.hashCode, x1), x2), 1) - 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) = + final def addDelta(elemHash: Int, delta: Int) = if (elemHash == NotCached) NotCached else avoidSpecialHashes(elemHash + delta) @@ -101,3 +79,25 @@ trait Hashable { else if (h == HashUnknown) HashUnknownAlt else h } + +object Hashing extends Hashing { + + /** A hash value indicating that the underlying type is not + * cached in uniques. + */ + final val NotCached = 0 + + /** An alternative value returned from `hash` if the + * computed hashCode would be `NotCached`. + */ + private[core] final val NotCachedAlt = Int.MinValue + + /** A value that indicates that the hash code is unknown + */ + private[core] final val HashUnknown = 1234 + + /** An alternative value if computeHash would otherwise yield HashUnknown + */ + private[core] final val HashUnknownAlt = 4321 +} + diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 05dc572dc51e..31ed27e60c05 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -78,7 +78,7 @@ object TypeErasure { */ abstract case class ErasedValueType(tycon: TypeRef, erasedUnderlying: Type) extends CachedGroundType with ValueType { - override def computeHash = doHash(tycon, erasedUnderlying) + override def computeHash(h: Hashing) = h.doHash(getClass, tycon, erasedUnderlying) } final class CachedErasedValueType(tycon: TypeRef, erasedUnderlying: Type) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e7f3acf4d033..982d2b9a53b5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -27,7 +27,6 @@ import printing.Texts._ import ast.untpd import dotty.tools.dotc.transform.Erasure import printing.Printer -import Hashable._ import Uniques._ import collection.{mutable, Seq, breakOut} import config.Config @@ -84,7 +83,7 @@ object Types { * * Note: please keep in sync with copy in `docs/docs/internals/type-system.md`. */ - abstract class Type extends DotClass with Hashable with printing.Showable { + abstract class Type extends DotClass with printing.Showable { // ----- Tests ----------------------------------------------------- @@ -1437,37 +1436,37 @@ object Types { /** Instances of this class are cached and are not proxies. */ abstract class CachedGroundType extends Type with CachedType { - private[this] var myHash = HashUnknown + private[this] var myHash = Hashing.HashUnknown final def hash = { - if (myHash == HashUnknown) { - myHash = computeHash - assert(myHash != HashUnknown) + if (myHash == Hashing.HashUnknown) { + myHash = computeHash(Hashing) + assert(myHash != Hashing.HashUnknown) } myHash } override final def hashCode = - if (hash == NotCached) System.identityHashCode(this) else hash - def computeHash: Int + if (hash == Hashing.NotCached) System.identityHashCode(this) else hash + def computeHash(h: Hashing): Int } /** Instances of this class are cached and are proxies. */ abstract class CachedProxyType extends TypeProxy with CachedType { - protected[this] var myHash = HashUnknown + protected[this] var myHash = Hashing.HashUnknown final def hash = { - if (myHash == HashUnknown) { - myHash = computeHash - assert(myHash != HashUnknown) + if (myHash == Hashing.HashUnknown) { + myHash = computeHash(Hashing) + assert(myHash != Hashing.HashUnknown) } myHash } override final def hashCode = - if (hash == NotCached) System.identityHashCode(this) else hash - def computeHash: Int + if (hash == Hashing.NotCached) System.identityHashCode(this) else hash + def computeHash(h: Hashing): Int } /** Instances of this class are uncached and are not proxies. */ abstract class UncachedGroundType extends Type { - final def hash = NotCached + final def hash = Hashing.NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") @@ -1476,7 +1475,7 @@ object Types { /** Instances of this class are uncached and are proxies. */ abstract class UncachedProxyType extends TypeProxy { - final def hash = NotCached + final def hash = Hashing.NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") @@ -2016,7 +2015,7 @@ object Types { false } - override def computeHash = unsupported("computeHash") + override def computeHash(h: Hashing) = h.doHash(getClass, designator, prefix) override def eql(that: Type) = this eq that // safe because named types are hash-consed separately } @@ -2140,7 +2139,7 @@ object Types { // can happen in IDE if `cls` is stale } - override def computeHash = doHash(tref) + override def computeHash(h: Hashing) = h.doHash(getClass, tref) override def eql(that: Type) = that match { case that: ThisType => tref.eq(that.tref) @@ -2168,7 +2167,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(h: Hashing) = h.doHash(getClass, thistpe, supertpe) override def eql(that: Type) = that match { case that: SuperType => thistpe.eq(that.thistpe) && supertpe.eq(that.supertpe) @@ -2189,7 +2188,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(h: Hashing) = h.doHash(getClass, value) } final class CachedConstantType(value: Constant) extends ConstantType(value) @@ -2253,7 +2252,7 @@ object Types { if (parent.member(refinedName).exists) derivedRefinedType(parent, refinedName, refinedInfo) else parent - override def computeHash = doHash(refinedName, refinedInfo, parent) + override def computeHash(h: Hashing) = h.doHash(getClass, refinedName, refinedInfo, parent) override def eql(that: Type) = that match { case that: RefinedType => @@ -2317,7 +2316,7 @@ object Types { refacc.apply(false, tp) } - override def computeHash = doHash(parent) + override def computeHash(h: Hashing) = h.doHash(getClass, parent) override def equals(that: Any) = that match { case that: RecType => parent == that.parent @@ -2423,7 +2422,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(h: Hashing) = h.doHash(getClass, tp1, tp2) override def eql(that: Type) = that match { case that: AndType => tp1.eq(that.tp1) && tp2.eq(that.tp2) @@ -2484,7 +2483,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(h: Hashing) = h.doHash(getClass, tp1, tp2) override def eql(that: Type) = that match { case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) @@ -2550,7 +2549,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(h: Hashing) = h.doHash(getClass, resType) override def eql(that: Type) = that match { case that: ExprType => resType.eq(that.resType) @@ -2634,7 +2633,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(h: Hashing) = h.doHash(getClass, paramNames, resType, paramInfos) final override def equals(that: Any) = that match { case that: HKLambda => @@ -2791,7 +2790,7 @@ object Types { def computeSignature(implicit ctx: Context): Signature = resultSignature.prepend(paramInfos, isJavaMethod) - final override def computeHash = doHash(paramNames, resType, paramInfos) + final override def computeHash(h: Hashing) = h.doHash(getClass, paramNames, resType, paramInfos) final override def equals(that: Any) = that match { case that: MethodType => @@ -3133,7 +3132,7 @@ object Types { final class CachedAppliedType(tycon: Type, args: List[Type], hc: Int) extends AppliedType(tycon, args) { myHash = hc - override def computeHash = unsupported("computeHash") + override def computeHash(h: Hashing) = h.doHash(getClass, tycon, args) override def eql(that: Type) = this eq that // safe because applied types are hash-consed separately } @@ -3163,7 +3162,7 @@ object Types { else infos(paramNum) } - override def computeHash = doHash(paramNum, binder.identityHash) + override def computeHash(h: Hashing) = h.doHash(getClass, paramNum, h.identityHash(binder)) override def equals(that: Any) = that match { case that: ParamRef => binder.eq(that.binder) && paramNum == that.paramNum @@ -3220,7 +3219,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(h: Hashing) = h.addDelta(h.identityHash(binder), 41) override def equals(that: Any) = that match { case that: RecThis => binder.eq(that.binder) @@ -3241,7 +3240,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 } @@ -3276,6 +3275,7 @@ object Types { * * `owningTree` and `owner` are used to determine whether a type-variable can be instantiated * at some given point. See `Inferencing#interpolateUndetVars`. + * ??? make uncached ??? */ final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, val bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType { @@ -3347,7 +3347,7 @@ object Types { } } - override def computeHash: Int = identityHash + override def computeHash(h: Hashing): Int = h.identityHash(this) override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) override def toString = { @@ -3422,7 +3422,7 @@ object Types { if ((prefix eq this.prefix) && (classParents eq this.classParents) && (decls eq this.decls) && (selfInfo eq this.selfInfo)) this else ClassInfo(prefix, cls, classParents, decls, selfInfo) - override def computeHash = doHash(cls, prefix) + override def computeHash(h: Hashing) = h.doHash(getClass, cls, prefix) override def eql(that: Type) = that match { case that: ClassInfo => @@ -3505,7 +3505,7 @@ object Types { case _ => super.| (that) } - override def computeHash = doHash(lo, hi) + override def computeHash(h: Hashing) = h.doHash(getClass, lo, hi) override def equals(that: Any): Boolean = that match { case that: TypeAlias => false @@ -3528,7 +3528,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(h: Hashing) = h.doHash(getClass, alias) override def equals(that: Any): Boolean = that match { case that: TypeAlias => alias == that.alias @@ -3587,7 +3587,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(h: Hashing) = h.doHash(getClass, elemType) override def eql(that: Type) = that match { case that: JavaArrayType => elemType.eq(that.elemType) @@ -3605,12 +3605,12 @@ object Types { /** Sentinel for "missing type" */ @sharable case object NoType extends CachedGroundType { override def exists = false - override def computeHash = hashSeed + override def computeHash(h: Hashing) = getClass.hashCode } /** Missing prefix */ @sharable case object NoPrefix extends CachedGroundType { - override def computeHash = hashSeed + override def computeHash(h: Hashing) = getClass.hashCode } /** A common superclass of `ErrorType` and `TryDynamicCallSite`. Instances of this @@ -3650,7 +3650,7 @@ object Types { else if (!optBounds.exists) WildcardType else WildcardType(optBounds.asInstanceOf[TypeBounds]) - override def computeHash = doHash(optBounds) + override def computeHash(h: Hashing) = h.doHash(getClass, optBounds) override def eql(that: Type) = that match { case that: WildcardType => optBounds.eq(that.optBounds) diff --git a/compiler/src/dotty/tools/dotc/core/Uniques.scala b/compiler/src/dotty/tools/dotc/core/Uniques.scala index 36228f418b9e..d94bfa526c4d 100644 --- a/compiler/src/dotty/tools/dotc/core/Uniques.scala +++ b/compiler/src/dotty/tools/dotc/core/Uniques.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc package core -import Types._, Symbols._, Contexts._, util.Stats._, Hashable._, Names._ +import Types._, Symbols._, Contexts._, util.Stats._, Hashing._, Names._ import config.Config import Decorators._ import util.HashSet @@ -41,7 +41,7 @@ object Uniques { ) */ - final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity) with Hashable { + final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity) { override def hash(x: NamedType): Int = x.hash private def findPrevious(h: Int, prefix: Type, designator: Designator): NamedType = { @@ -54,7 +54,7 @@ object Uniques { } def enterIfNew(prefix: Type, designator: Designator, isTerm: Boolean)(implicit ctx: Context): NamedType = { - val h = doHash(designator, prefix) + val h = Hashing.doHash(classOf[NamedType], designator, prefix) if (monitored) recordCaching(h, classOf[NamedType]) def newType = if (isTerm) new CachedTermRef(prefix, designator, h) @@ -67,7 +67,7 @@ object Uniques { } } - final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity) with Hashable { + final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity) { override def hash(x: AppliedType): Int = x.hash private def findPrevious(h: Int, tycon: Type, args: List[Type]): AppliedType = { @@ -80,7 +80,7 @@ object Uniques { } def enterIfNew(tycon: Type, args: List[Type]): AppliedType = { - val h = doHash(tycon, args) + val h = Hashing.doHash(classOf[AppliedType], tycon, args) def newType = new CachedAppliedType(tycon, args, h) if (monitored) recordCaching(h, classOf[CachedAppliedType]) if (h == NotCached) newType diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index bceb0c447067..08fc531c748d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -29,7 +29,6 @@ import ErrorReporting._ import reporting.diagnostic.{Message, MessageContainer} import Inferencing.fullyDefinedType import Trees._ -import Hashable._ import util.Property import config.Config import config.Printers.{implicits, implicitsDetailed, typr} @@ -210,7 +209,7 @@ object Implicits { /** The implicit references that are eligible for type `tp`. */ def eligible(tp: Type): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ { - if (tp.hash == NotCached) computeEligible(tp) + if (tp.hash == Hashing.NotCached) computeEligible(tp) else eligibleCache get tp match { case Some(eligibles) => def elided(ci: ContextualImplicits): Int = { @@ -469,7 +468,7 @@ trait ImplicitRunInfo { self: Run => * @param isLifted Type `tp` is the result of a `liftToClasses` application */ def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = { - val canCache = Config.cacheImplicitScopes && tp.hash != NotCached + val canCache = Config.cacheImplicitScopes && tp.hash != Hashing.NotCached def computeIScope() = { val savedEphemeral = ctx.typerState.ephemeral ctx.typerState.ephemeral = false diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index b975fe51c198..a2fe8d0f95a2 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(h: Hashing) = { val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0) - addDelta(doHash(name, memberProto), delta) + h.addDelta(h.doHash(getClass, name, memberProto), delta) } } @@ -326,7 +326,7 @@ object ProtoTypes { } class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) { - override def computeHash = doHash(argType, resultType) + override def computeHash(h: Hashing) = h.doHash(getClass, argType, resultType) } object ViewProto { diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala index 5123cb34a841..2632cfe181d3 100644 --- a/compiler/test/dotc/tests.scala +++ b/compiler/test/dotc/tests.scala @@ -310,7 +310,7 @@ class tests extends CompilerTest { @Test def tasty_core = compileList("tasty_core", List( "Annotations.scala", "Constants.scala", "Constraint.scala", "ConstraintHandling.scala", "ConstraintRunInfo.scala", "Contexts.scala", "Decorators.scala", "Definitions.scala", - "DenotTransformers.scala", "Denotations.scala", "Flags.scala", "Hashable.scala", + "DenotTransformers.scala", "Denotations.scala", "Flags.scala", "Hashing.scala", "NameOps.scala", "Names.scala", "OrderingConstraint.scala", "Periods.scala", "Phases.scala", "Scopes.scala", "Signature.scala", "StdNames.scala", "Substituters.scala", "SymDenotations.scala", "SymbolLoaders.scala", "Symbols.scala", From 7f9745355992015ffcc60816540d5d060392c3a9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 17:26:50 +0100 Subject: [PATCH 2/8] Suppress automatic printing of TyperState#hashesStr --- compiler/src/dotty/tools/dotc/config/Printers.scala | 1 + .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 10 +++++----- compiler/src/dotty/tools/dotc/core/TyperState.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index d914d4b6d885..1c24884dea8f 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -3,6 +3,7 @@ package dotty.tools.dotc.config object Printers { class Printer { + val verbose = false def println(msg: => String): Unit = System.out.println(msg) } diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 2fda43a675a9..af5deb670695 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -92,12 +92,12 @@ trait ConstraintHandling { if (Config.failOnInstantiationToNothing) assert(false, msg) else ctx.log(msg) } - constr.println(i"adding $description in ${ctx.typerState.hashesStr}") + constr.println(i"adding $description${ctx.typerState.hashesStr(constr)}") val lower = constraint.lower(param) val res = addOneBound(param, bound, isUpper = true) && lower.forall(addOneBound(_, bound, isUpper = true)) - constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") + constr.println(i"added $description = $res${ctx.typerState.hashesStr(constr)}") res } @@ -108,7 +108,7 @@ trait ConstraintHandling { val res = addOneBound(param, bound, isUpper = false) && upper.forall(addOneBound(_, bound, isUpper = false)) - constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") + constr.println(i"added $description = $res${ctx.typerState.hashesStr(constr)}") res } @@ -121,12 +121,12 @@ trait ConstraintHandling { val up2 = p2 :: constraint.exclusiveUpper(p2, p1) val lo1 = constraint.nonParamBounds(p1).lo val hi2 = constraint.nonParamBounds(p2).hi - constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}") + constr.println(i"adding $description down1 = $down1, up2 = $up2${ctx.typerState.hashesStr(constr)}") constraint = constraint.addLess(p1, p2) down1.forall(addOneBound(_, hi2, isUpper = true)) && up2.forall(addOneBound(_, lo1, isUpper = false)) } - constr.println(i"added $description = $res ${ctx.typerState.hashesStr}") + constr.println(i"added $description = $res${ctx.typerState.hashesStr(constr)}") res } diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 1821735522f5..6ee31c0969f9 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -187,6 +187,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab override def toText(printer: Printer): Text = constraint.toText(printer) - def hashesStr: String = - if (previous == null) "" else hashCode.toString + " -> " + previous.hashesStr + def hashesStr(p: config.Printers.Printer): String = + if (!p.verbose || previous == null) "" else " " + hashCode.toString + " ->" + previous.hashesStr(p) } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 08fc531c748d..95f79815505c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -778,7 +778,7 @@ trait Implicits { self: Typer => case result: SearchSuccess => result.tstate.commit() implicits.println(i"success: $result") - implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}") + implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint}${ctx.typerState.hashesStr(implicits)}") result case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto From c275373c156b4b9f68237283e07305ae69cc3df9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 17:32:38 +0100 Subject: [PATCH 3/8] Structural hashes for dependent types Make hashes structural for dependent types. Two isomorphic types should give the same hash even if there are dependencies to BindingTypes. --- .../src/dotty/tools/dotc/core/Hashing.scala | 25 ++++++++++++++++++- .../src/dotty/tools/dotc/core/Types.scala | 11 +++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Hashing.scala b/compiler/src/dotty/tools/dotc/core/Hashing.scala index 3959a3646fb8..6e19ff469032 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashing.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashing.scala @@ -12,7 +12,7 @@ abstract class Hashing { protected def typeHash(tp: Type) = tp.hash - final def identityHash(tp: Type) = avoidSpecialHashes(System.identityHashCode(tp)) + def identityHash(tp: Type) = avoidSpecialHashes(System.identityHashCode(tp)) protected def finishHash(seed: Int, arity: Int, tp: Type): Int = { val elemHash = typeHash(tp) @@ -78,6 +78,29 @@ abstract class Hashing { if (h == NotCached) NotCachedAlt else if (h == HashUnknown) HashUnknownAlt else h + + val binders: Array[BindingType] = Array() + + private class WithBinders(override val binders: Array[BindingType]) extends Hashing { + + override def typeHash(tp: Type) = tp.computeHash(this) + + override def identityHash(tp: Type) = { + var idx = 0 + while (idx < binders.length && (binders(idx) `ne` tp)) + idx += 1 + avoidSpecialHashes( + if (idx < binders.length) idx * 31 + else System.identityHashCode(binders)) + } + } + + def withBinder(binder: BindingType): Hashing = { + val newBinders = new Array[BindingType](binders.length + 1) + Array.copy(binders, 0, newBinders, 0, binders.length) + newBinders(binders.length) = binder + new WithBinders(newBinders) + } } object Hashing extends Hashing { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 982d2b9a53b5..f7e2419a5270 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1403,6 +1403,9 @@ object Types { */ def eql(that: Type): Boolean = this.equals(that) + /** Compute `hash` using given `Hashing` */ + def computeHash(h: Hashing): Int + } // end Type // ----- Type categories ---------------------------------------------- @@ -1446,7 +1449,6 @@ object Types { } override final def hashCode = if (hash == Hashing.NotCached) System.identityHashCode(this) else hash - def computeHash(h: Hashing): Int } /** Instances of this class are cached and are proxies. */ @@ -1461,25 +1463,26 @@ object Types { } override final def hashCode = if (hash == Hashing.NotCached) System.identityHashCode(this) else hash - def computeHash(h: Hashing): Int } /** Instances of this class are uncached and are not proxies. */ abstract class UncachedGroundType extends Type { - final def hash = Hashing.NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") } + final def hash = Hashing.NotCached + final def computeHash(h: Hashing): Int = Hashing.NotCached } /** Instances of this class are uncached and are proxies. */ abstract class UncachedProxyType extends TypeProxy { - final def hash = Hashing.NotCached if (monitored) { record(s"uncachable") record(s"uncachable: $getClass") } + final def hash = Hashing.NotCached + final def computeHash(h: Hashing): Int = Hashing.NotCached } /** A marker trait for types that apply only to type symbols */ From d8ee7bf0a69b5d675fc08870de5bed7543ed0bf2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 17:34:18 +0100 Subject: [PATCH 4/8] Structural equality for dependent types `equals` of two isomorphic types should return true, even if they are dependencies to BindingTypes. --- .../src/dotty/tools/dotc/core/Types.scala | 180 +++++++++++++----- .../dotty/tools/dotc/typer/ProtoTypes.scala | 19 +- 2 files changed, 151 insertions(+), 48 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f7e2419a5270..41fbdfd93a06 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1393,16 +1393,23 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) - /** customized hash code of this type. - * NotCached for uncached types. Cached types - * compute hash and use it as the type's hashCode. + final override def equals(that: Any) = StructEquality.equals(this, that) + + /** Is `this` isomorphic to that, using comparer `e`? + * It is assumed that `this ne that`. */ - def hash: Int + def iso(that: Any, e: StructEquality): Boolean = false /** Equality used for hash-consing; uses `eq` on all recursive invocations. */ def eql(that: Type): Boolean = this.equals(that) + /** customized hash code of this type. + * NotCached for uncached types. Cached types + * compute hash and use it as the type's hashCode. + */ + def hash: Int + /** Compute `hash` using given `Hashing` */ def computeHash(h: Hashing): Int @@ -2010,10 +2017,10 @@ object Types { } } - override def equals(that: Any) = that match { + override def iso(that: Any, e: StructEquality): Boolean = that match { case that: NamedType => this.designator == that.designator && - this.prefix == that.prefix + e.equals(this.prefix, that.prefix) case _ => false } @@ -2148,6 +2155,11 @@ object Types { case that: ThisType => tref.eq(that.tref) case _ => false } + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: ThisType => e.equals(tref, that.tref) + case _ => false + } } final class CachedThisType(tref: TypeRef) extends ThisType(tref) @@ -2176,6 +2188,12 @@ object Types { case that: SuperType => thistpe.eq(that.thistpe) && supertpe.eq(that.supertpe) case _ => false } + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: SuperType => + e.equals(thistpe, that.thistpe) && e.equals(supertpe, that.supertpe) + case _ => false + } } final class CachedSuperType(thistpe: Type, supertpe: Type) extends SuperType(thistpe, supertpe) @@ -2192,6 +2210,11 @@ object Types { override def underlying(implicit ctx: Context) = value.tpe override def computeHash(h: Hashing) = h.doHash(getClass, value) + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: ConstantType => value.equals(that.value) + case _ => false + } } final class CachedConstantType(value: Constant) extends ConstantType(value) @@ -2218,7 +2241,6 @@ object Types { def evaluating = computed && myRef == null override def underlying(implicit ctx: Context) = ref override def toString = s"LazyRef(${if (computed) myRef else "..."})" - override def equals(other: Any) = this.eq(other.asInstanceOf[AnyRef]) override def hashCode = System.identityHashCode(this) } @@ -2264,6 +2286,14 @@ object Types { parent.eq(that.parent) case _ => false } + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: RefinedType => + refinedName.eq(that.refinedName) && + e.equals(refinedInfo, that.refinedInfo) && + e.equals(parent, that.parent) + case _ => false + } } class CachedRefinedType(parent: Type, refinedName: Name, refinedInfo: Type) @@ -2319,15 +2349,16 @@ object Types { refacc.apply(false, tp) } - override def computeHash(h: Hashing) = h.doHash(getClass, parent) + override def computeHash(h: Hashing) = h.withBinder(this).doHash(getClass, 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, e: StructEquality) = that match { + case that: RecType => + e.withBinders(this, that).equals(parent, that.parent) case _ => false } @@ -2431,6 +2462,11 @@ object Types { case that: AndType => tp1.eq(that.tp1) && tp2.eq(that.tp2) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: AndType => e.equals(tp1, that.tp1) && e.equals(tp2, that.tp2) + case _ => false + } } final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) @@ -2492,6 +2528,11 @@ object Types { case that: OrType => tp1.eq(that.tp1) && tp2.eq(that.tp2) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: OrType => e.equals(tp1, that.tp1) && e.equals(tp2, that.tp2) + case _ => false + } } final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) @@ -2558,6 +2599,11 @@ object Types { case that: ExprType => resType.eq(that.resType) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: ExprType => e.equals(resType, that.resType) + case _ => false + } } final class CachedExprType(resultType: Type) extends ExprType(resultType) @@ -2636,30 +2682,33 @@ object Types { abstract class HKLambda extends CachedProxyType with LambdaType { final override def underlying(implicit ctx: Context) = resType - final override def computeHash(h: Hashing) = h.doHash(getClass, paramNames, resType, paramInfos) + final override def computeHash(h: Hashing) = + h.withBinder(this).doHash(getClass, paramNames, resType, paramInfos) - final override def equals(that: Any) = that match { + final override def 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, e: StructEquality) = 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 e1 = e.withBinders(this, that) + e1.equals(paramInfos, that.paramInfos) && + e1.equals(resType, that.resType) + } case _ => false } } - trait MethodOrPoly extends LambdaType with MethodicType + trait MethodOrPoly extends LambdaType with MethodicType // TODO: Make PolyTypes cached trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ @@ -2795,23 +2844,25 @@ object Types { final override def computeHash(h: Hashing) = h.doHash(getClass, paramNames, resType, paramInfos) - final override def equals(that: Any) = that match { + final override def eql(that: Type) = that match { case that: MethodType => - paramNames == that.paramNames && - paramInfos == that.paramInfos && - resType == that.resType && + paramNames.eqElements(that.paramNames) && + paramInfos.eqElements(that.paramInfos) && + resType.eq(that.resType) && companion.eq(that.companion) case _ => false } - final override def eql(that: Type) = that match { + final override def iso(that: Any, e: StructEquality) = that match { case that: MethodType => paramNames.eqElements(that.paramNames) && - paramInfos.eqElements(that.paramInfos) && - resType.eq(that.resType) && - companion.eq(that.companion) - case _ => + companion.eq(that.companion) && { + val e1 = e.withBinders(this, that) + e1.equals(paramInfos, that.paramInfos) && + e1.equals(resType, that.resType) + } + case _ => false } @@ -3003,6 +3054,18 @@ object Types { case _ => this } + final override def iso(that: Any, e: StructEquality) = that match { // TODO: Move up to MethodOrPoly + case that: PolyType => + paramNames.eqElements(that.paramNames) && + companion.eq(that.companion) && { + val e1 = e.withBinders(this, that) + e1.equals(paramInfos, that.paramInfos) && + e1.equals(resType, that.resType) + } + case _ => + false + } + protected def prefixString = "PolyType" } @@ -3131,12 +3194,18 @@ object Types { def derivedAppliedType(tycon: Type, args: List[Type])(implicit ctx: Context): Type = if ((tycon eq this.tycon) && (args eq this.args)) this else tycon.appliedTo(args) + + override def computeHash(h: Hashing) = h.doHash(getClass, tycon, args) + override def eql(that: Type) = this eq that // safe because applied types are hash-consed separately + + final override def iso(that: Any, e: StructEquality) = that match { + case that: AppliedType => e.equals(tycon, that.tycon) && e.equals(args, that.args) + case _ => false + } } final class CachedAppliedType(tycon: Type, args: List[Type], hc: Int) extends AppliedType(tycon, args) { myHash = hc - override def computeHash(h: Hashing) = h.doHash(getClass, tycon, args) - override def eql(that: Type) = this eq that // safe because applied types are hash-consed separately } object AppliedType { @@ -3167,8 +3236,8 @@ object Types { override def computeHash(h: Hashing) = h.doHash(getClass, paramNum, h.identityHash(binder)) - override def equals(that: Any) = that match { - case that: ParamRef => binder.eq(that.binder) && paramNum == that.paramNum + override def iso(that: Any, e: StructEquality) = that match { + case that: ParamRef => e.equalBinders(binder, that.binder) && paramNum == that.paramNum case _ => false } @@ -3224,8 +3293,8 @@ object Types { // between RecTypes and RecRefs. override def computeHash(h: Hashing) = h.addDelta(h.identityHash(binder), 41) - override def equals(that: Any) = that match { - case that: RecThis => binder.eq(that.binder) + override def iso(that: Any, e: StructEquality) = that match { + case that: RecThis => e.equalBinders(binder, that.binder) case _ => false } @@ -3244,7 +3313,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 } @@ -3351,7 +3419,6 @@ object Types { } override def computeHash(h: Hashing): Int = h.identityHash(this) - override def equals(that: Any) = this.eq(that.asInstanceOf[AnyRef]) override def toString = { def instStr = if (inst.exists) s" -> $inst" else "" @@ -3437,6 +3504,16 @@ object Types { case _ => false } + override def iso(that: Any, e: StructEquality) = that match { + case that: ClassInfo => + e.equals(prefix, that.prefix) && + cls.eq(that.cls) && + e.equals(classParents, that.classParents) && + decls.eq(that.decls) && + selfInfo.eq(that.selfInfo) + case _ => false + } + override def toString = s"ClassInfo($prefix, $cls, $classParents)" } @@ -3510,9 +3587,9 @@ object Types { override def computeHash(h: Hashing) = h.doHash(getClass, lo, hi) - override def equals(that: Any): Boolean = that match { + override def iso(that: Any, e: StructEquality): Boolean = that match { case that: TypeAlias => false - case that: TypeBounds => lo == that.lo && hi == that.hi + case that: TypeBounds => e.equals(lo, that.lo) && e.equals(hi, that.hi) case _ => false } @@ -3533,8 +3610,8 @@ object Types { override def computeHash(h: Hashing) = h.doHash(getClass, alias) - override def equals(that: Any): Boolean = that match { - case that: TypeAlias => alias == that.alias + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: TypeAlias => e.equals(alias, that.alias) case _ => false } @@ -3576,6 +3653,11 @@ object Types { derivedAnnotatedType(tpe.stripTypeVar, annot) override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots + + override def iso(that: Any, e: StructEquality): Boolean = that match { + case that: AnnotatedType => e.equals(tpe, that.tpe) && (annot `eq` that.annot) + case _ => false + } } object AnnotatedType { @@ -3596,6 +3678,11 @@ object Types { case that: JavaArrayType => elemType.eq(that.elemType) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: JavaArrayType => e.equals(elemType, that.elemType) + case _ => false + } } final class CachedJavaArrayType(elemType: Type) extends JavaArrayType(elemType) object JavaArrayType { @@ -3659,6 +3746,11 @@ object Types { case that: WildcardType => optBounds.eq(that.optBounds) case _ => false } + + override def iso(that: Any, e: StructEquality) = that match { + case that: WildcardType => e.equals(optBounds, that.optBounds) + case _ => false + } } final class CachedWildcardType(optBounds: Type) extends WildcardType(optBounds) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index a2fe8d0f95a2..764900f3e5be 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -118,9 +118,12 @@ object ProtoTypes { if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this else SelectionProto(name, memberProto, compat, privateOK) - override def equals(that: Any): Boolean = that match { + override def iso(that: Any, e: StructEquality): Boolean = that match { case that: SelectionProto => - (name eq that.name) && (memberProto == that.memberProto) && (compat eq that.compat) && (privateOK == that.privateOK) + (name `eq` that.name) && + e.equals(memberProto, that.memberProto) && + (compat `eq` that.compat) && + (privateOK == that.privateOK) case _ => false } @@ -397,9 +400,17 @@ object ProtoTypes { tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner)) } - val added = - if (state.constraint contains tl) tl.newLikeThis(tl.paramNames, tl.paramInfos, tl.resultType) + /** Ensure that `tl` is not already in constraint. If necessary, + * make a copy of `tl` by turning one of the bounds into a `LazyRef` + */ + def ensureFresh(tl: TypeLambda): TypeLambda = + if (state.constraint contains tl) { + val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos + val newParamInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1 + ensureFresh(tl.newLikeThis(tl.paramNames, newParamInfos, tl.resultType)) + } else tl + val added = ensureFresh(tl) val tvars = if (addTypeVars) newTypeVars(added) else Nil ctx.typeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) (added, tvars) From aa04a3872f3fd1779293a1d8aa8ff27431efdbc9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 17:37:57 +0100 Subject: [PATCH 5/8] Add test cases for #3965 What happened was that a higher-kinded type was added repeatedly to a constraint. Because equality of higher-kinded types was broken, the compiler did not realize that the type had already been added. --- tests/pos/i3965.scala | 19 +++++++++++++++++++ tests/pos/i3965a.scala | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/pos/i3965.scala create mode 100644 tests/pos/i3965a.scala diff --git a/tests/pos/i3965.scala b/tests/pos/i3965.scala new file mode 100644 index 000000000000..e5aec615e71d --- /dev/null +++ b/tests/pos/i3965.scala @@ -0,0 +1,19 @@ +trait Iterable[+A] extends IterableOps[A, Iterable, Iterable[A]] +trait IterableOps[+A, +CCop[_], +C] + +trait SortedSet[A] extends Iterable[A] with SortedSetOps[A, SortedSet, SortedSet[A]] + +trait SortedSetOps[A, +CCss[X] <: SortedSet[X], +C <: SortedSetOps[A, CCss, C]] + +class TreeSet[A] + extends SortedSet[A] + with SortedSetOps[A, TreeSet, TreeSet[A]] + +class Test { + def optionSequence1[CCos[X] <: IterableOps[X, CCos, _], A](xs: CCos[Option[A]]): Option[CCos[A]] = ??? + def optionSequence1[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], A : Ordering](xs: CC[Option[A]]): Option[CC[A]] = ??? + + def test(xs2: TreeSet[Option[String]]) = { + optionSequence1(xs2) + } +} diff --git a/tests/pos/i3965a.scala b/tests/pos/i3965a.scala new file mode 100644 index 000000000000..eff026f951d1 --- /dev/null +++ b/tests/pos/i3965a.scala @@ -0,0 +1,15 @@ +trait SortedSet[A] extends SortedSetOps[A, SortedSet, SortedSet[A]] + +trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, CC, C]] + +class TreeSet[A] + extends SortedSet[A] + with SortedSetOps[A, TreeSet, TreeSet[A]] + +class Test { + def optionSequence1[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], A : Ordering](xs: CC[A]): Unit = () + + def test(xs2: TreeSet[String]) = { + optionSequence1(xs2) + } +} From 8c4d87393eac863e445367340d585036109fd8f3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 18:00:05 +0100 Subject: [PATCH 6/8] Hash-cons all lambda types --- .../tools/dotc/core/StructEquality.scala | 36 +++++++++ .../src/dotty/tools/dotc/core/Types.scala | 76 ++++++------------- 2 files changed, 61 insertions(+), 51 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/StructEquality.scala diff --git a/compiler/src/dotty/tools/dotc/core/StructEquality.scala b/compiler/src/dotty/tools/dotc/core/StructEquality.scala new file mode 100644 index 000000000000..7b04720bde38 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/StructEquality.scala @@ -0,0 +1,36 @@ +package dotty.tools.dotc +package core + +import annotation.tailrec +import Types._ + +class StructEquality(binders1: Array[BindingType], binders2: Array[BindingType]) { + + def equals(tp1: Type, tp2: Any): Boolean = (tp1 `eq` tp2.asInstanceOf[AnyRef]) || tp1.iso(tp2, this) + + @tailrec final def equals(tps1: List[Type], tps2: List[Type]): Boolean = + (tps1 `eq` tps2) || { + if (tps1.isEmpty) tps2.isEmpty + else tps2.nonEmpty && equals(tps1.head, tps2.head) && equals(tps1.tail, tps2.tail) + } + + final def equalBinders(tp1: BindingType, tp2: BindingType): Boolean = + (tp1 `eq` tp2) || { + var idx = 0 + while (idx < binders1.length && (tp1 `ne` binders1(idx))) + idx += 1 + idx < binders2.length && (tp2 `eq` binders2(idx)) + } + + final def withBinders(binder1: BindingType, binder2: BindingType): StructEquality = { + val newBinders1 = new Array[BindingType](binders1.length + 1) + val newBinders2 = new Array[BindingType](binders2.length + 1) + Array.copy(binders1, 0, newBinders1, 0, binders1.length) + Array.copy(binders2, 0, newBinders2, 0, binders2.length) + newBinders1(binders1.length) = binder1 + newBinders2(binders1.length) = binder2 + new StructEquality(newBinders1, newBinders2) + } +} + +object StructEquality extends StructEquality(Array(), Array()) \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 41fbdfd93a06..38db0e42b069 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2351,11 +2351,6 @@ object Types { override def computeHash(h: Hashing) = h.withBinder(this).doHash(getClass, parent) - override def eql(that: Type) = that match { - case that: RecType => parent.eq(that.parent) - case _ => false - } - override def iso(that: Any, e: StructEquality) = that match { case that: RecType => e.withBinders(this, that).equals(parent, that.parent) @@ -2679,37 +2674,51 @@ object Types { final override def toString = s"$prefixString($paramNames, $paramInfos, $resType)" } + /** Base class of HKTypeLambda. In the future could be base class of HKTermLambda + * (aka typelevel function type) if that is added. + */ abstract class HKLambda extends CachedProxyType with LambdaType { final override def underlying(implicit ctx: Context) = resType final override def computeHash(h: Hashing) = h.withBinder(this).doHash(getClass, paramNames, resType, paramInfos) - final override def eql(that: Type) = that match { + final override def iso(that: Any, e: StructEquality) = 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 e1 = e.withBinders(this, that) + e1.equals(paramInfos, that.paramInfos) && + e1.equals(resType, that.resType) + } case _ => false } + } + + /** Common base class of MethodType and PolyType. It's methods are duplicated + * from HKLambda for efficiency. Joining the two methods in the common supertrait + * LambdaType could be slower because of trait dispatch and because the type test + * in `iso` would be to a trait instead of a class + */ + abstract class MethodOrPoly extends CachedGroundType with LambdaType with MethodicType { + + final override def computeHash(h: Hashing) = + h.withBinder(this).doHash(getClass, paramNames, resType, paramInfos) final override def iso(that: Any, e: StructEquality) = that match { - case that: HKLambda => + case that: MethodOrPoly => paramNames.eqElements(that.paramNames) && companion.eq(that.companion) && { val e1 = e.withBinders(this, that) e1.equals(paramInfos, that.paramInfos) && e1.equals(resType, that.resType) } - case _ => + case _ => false } } - trait MethodOrPoly extends LambdaType with MethodicType // TODO: Make PolyTypes cached - trait TermLambda extends LambdaType { thisLambdaType => import DepStatus._ type ThisName = TermName @@ -2825,7 +2834,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 @@ -2842,29 +2851,6 @@ object Types { def computeSignature(implicit ctx: Context): Signature = resultSignature.prepend(paramInfos, isJavaMethod) - final override def computeHash(h: Hashing) = h.doHash(getClass, paramNames, resType, paramInfos) - - 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 - } - - final override def iso(that: Any, e: StructEquality) = that match { - case that: MethodType => - paramNames.eqElements(that.paramNames) && - companion.eq(that.companion) && { - val e1 = e.withBinders(this, that) - e1.equals(paramInfos, that.paramInfos) && - e1.equals(resType, that.resType) - } - case _ => - false - } protected def prefixString = "MethodType" } @@ -3023,7 +3009,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 @@ -3054,18 +3040,6 @@ object Types { case _ => this } - final override def iso(that: Any, e: StructEquality) = that match { // TODO: Move up to MethodOrPoly - case that: PolyType => - paramNames.eqElements(that.paramNames) && - companion.eq(that.companion) && { - val e1 = e.withBinders(this, that) - e1.equals(paramInfos, that.paramInfos) && - e1.equals(resType, that.resType) - } - case _ => - false - } - protected def prefixString = "PolyType" } From f913c4349455bf7e1c063e01cb97083916d1208b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 18:46:22 +0100 Subject: [PATCH 7/8] Merge common methods of AndType and OrType --- .../src/dotty/tools/dotc/core/Types.scala | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 38db0e42b069..fd7fa6dc64d3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2398,11 +2398,11 @@ object Types { // --- AndType/OrType --------------------------------------------------------------- - trait AndOrType extends ValueType { // todo: check where we can simplify using AndOrType + abstract class AndOrType extends CachedGroundType with ValueType { def tp1: Type def tp2: Type def isAnd: Boolean - def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type // needed? + def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type private[this] var myBaseClassesPeriod: Period = Nowhere private[this] var myBaseClasses: List[ClassSymbol] = _ @@ -2434,9 +2434,21 @@ object Types { } myBaseClasses } + + override def computeHash(h: Hashing) = h.doHash(getClass, tp1, tp2) + + override def eql(that: Type) = that match { + case that: AndOrType => isAnd == that.isAnd && tp1.eq(that.tp1) && tp2.eq(that.tp2) + case _ => false + } + + override def iso(that: Any, e: StructEquality) = that match { + case that: AndOrType => isAnd == that.isAnd && e.equals(tp1, that.tp1) && e.equals(tp2, that.tp2) + case _ => false + } } - abstract case class AndType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType { def isAnd = true @@ -2451,17 +2463,6 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedAndType(tp1, tp2) - override def computeHash(h: Hashing) = h.doHash(getClass, 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, e: StructEquality) = that match { - case that: AndType => e.equals(tp1, that.tp1) && e.equals(tp2, that.tp2) - case _ => false - } } final class CachedAndType(tp1: Type, tp2: Type) extends AndType(tp1, tp2) @@ -2490,7 +2491,7 @@ object Types { if (checkValid) apply(tp1, tp2) else unchecked(tp1, tp2) } - abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType { assert(tp1.isInstanceOf[ValueTypeOrWildcard] && tp2.isInstanceOf[ValueTypeOrWildcard], s"$tp1 $tp2") @@ -2516,18 +2517,6 @@ object Types { def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = derivedOrType(tp1, tp2) - - override def computeHash(h: Hashing) = h.doHash(getClass, 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, e: StructEquality) = that match { - case that: OrType => e.equals(tp1, that.tp1) && e.equals(tp2, that.tp2) - case _ => false - } } final class CachedOrType(tp1: Type, tp2: Type) extends OrType(tp1, tp2) @@ -2696,7 +2685,7 @@ object Types { } } - /** Common base class of MethodType and PolyType. It's methods are duplicated + /** Common base class of MethodType and PolyType. Its methods are duplicated * from HKLambda for efficiency. Joining the two methods in the common supertrait * LambdaType could be slower because of trait dispatch and because the type test * in `iso` would be to a trait instead of a class From 0423cf1ba3662deb3260f1967e21297c2b3ec6da Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Feb 2018 21:58:24 +0100 Subject: [PATCH 8/8] Make Hashing use only monomorphic dispatch Let's see whether this helps combat the observed performance hit. --- .../src/dotty/tools/dotc/core/Hashing.scala | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Hashing.scala b/compiler/src/dotty/tools/dotc/core/Hashing.scala index 6e19ff469032..bab8fc0cc8f3 100644 --- a/compiler/src/dotty/tools/dotc/core/Hashing.scala +++ b/compiler/src/dotty/tools/dotc/core/Hashing.scala @@ -4,29 +4,44 @@ package core import Types._ import scala.util.hashing.{ MurmurHash3 => hashing } -abstract class Hashing { +class Hashing(binders: Array[BindingType]) { import Hashing._ - final def finishHash(hashCode: Int, arity: Int): Int = + private def avoidSpecialHashes(h: Int) = + if (h == NotCached) NotCachedAlt + else if (h == HashUnknown) HashUnknownAlt + else h + + private def finishHash(hashCode: Int, arity: Int): Int = avoidSpecialHashes(hashing.finalizeHash(hashCode, arity)) - protected def typeHash(tp: Type) = tp.hash + private def typeHash(tp: Type) = + if (binders == null) tp.hash else tp.computeHash(this) - def identityHash(tp: Type) = avoidSpecialHashes(System.identityHashCode(tp)) + def identityHash(tp: Type): Int = { + if (binders != null) { + var idx = 0 + while (idx < binders.length) { + if (binders(idx) `eq` tp) return avoidSpecialHashes(idx * 31) + idx += 1 + } + } + avoidSpecialHashes(System.identityHashCode(tp)) + } - protected def finishHash(seed: Int, arity: Int, tp: Type): Int = { + private def finishHash(seed: Int, arity: Int, tp: Type): Int = { val elemHash = typeHash(tp) if (elemHash == NotCached) return NotCached finishHash(hashing.mix(seed, elemHash), arity + 1) } - protected def finishHash(seed: Int, arity: Int, tp1: Type, tp2: Type): Int = { + private def finishHash(seed: Int, arity: Int, tp1: Type, tp2: Type): Int = { val elemHash = typeHash(tp1) if (elemHash == NotCached) return NotCached finishHash(hashing.mix(seed, elemHash), arity + 1, tp2) } - protected def finishHash(seed: Int, arity: Int, tps: List[Type]): Int = { + private def finishHash(seed: Int, arity: Int, tps: List[Type]): Int = { var h = seed var xs = tps var len = arity @@ -40,7 +55,7 @@ abstract class Hashing { finishHash(h, len) } - protected def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = { + private def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = { val elemHash = typeHash(tp) if (elemHash == NotCached) return NotCached finishHash(hashing.mix(seed, elemHash), arity + 1, tps) @@ -74,36 +89,23 @@ abstract class Hashing { if (elemHash == NotCached) NotCached else avoidSpecialHashes(elemHash + delta) - private def avoidSpecialHashes(h: Int) = - if (h == NotCached) NotCachedAlt - else if (h == HashUnknown) HashUnknownAlt - else h - - val binders: Array[BindingType] = Array() - - private class WithBinders(override val binders: Array[BindingType]) extends Hashing { - - override def typeHash(tp: Type) = tp.computeHash(this) - - override def identityHash(tp: Type) = { - var idx = 0 - while (idx < binders.length && (binders(idx) `ne` tp)) - idx += 1 - avoidSpecialHashes( - if (idx < binders.length) idx * 31 - else System.identityHashCode(binders)) - } - } - - def withBinder(binder: BindingType): Hashing = { - val newBinders = new Array[BindingType](binders.length + 1) - Array.copy(binders, 0, newBinders, 0, binders.length) - newBinders(binders.length) = binder - new WithBinders(newBinders) + final def withBinder(binder: BindingType): Hashing = { + new Hashing( + if (binders == null) { + val bs = new Array[BindingType](1) + bs(0) = binder + bs + } + else { + val bs = new Array[BindingType](binders.length + 1) + Array.copy(binders, 0, bs, 0, binders.length) + bs(binders.length) = binder + bs + }) } } -object Hashing extends Hashing { +object Hashing extends Hashing(null) { /** A hash value indicating that the underlying type is not * cached in uniques.