Skip to content

Fix #3965: Make higher-kinded equality correct and efficient #3978

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 20, 2018
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) ||
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 38 additions & 25 deletions compiler/src/dotty/tools/dotc/core/Hashable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ package core

import Types._
import scala.util.hashing.{ MurmurHash3 => hashing }
import annotation.tailrec

object Hashable {

/** A null terminated list of BindingTypes. We use `null` here for efficiency */
class Binders(val tp: BindingType, val next: Binders)

/** A null terminated list of pairs of BindingTypes. Used for isomorphism tests. */
class BinderPairs(tp1: BindingType, tp2: BindingType, next: BinderPairs) {
@tailrec final def matches(t1: Type, t2: Type): Boolean =
(t1 `eq` tp1) && (t2 `eq` tp2) || next != null && next.matches(t1, t2)
}

/** A hash value indicating that the underlying type is not
* cached in uniques.
Expand Down Expand Up @@ -33,26 +43,29 @@ trait Hashable {
protected final def finishHash(hashCode: Int, arity: Int): Int =
avoidSpecialHashes(hashing.finalizeHash(hashCode, arity))

final def identityHash = avoidSpecialHashes(System.identityHashCode(this))
final def typeHash(bs: Binders, tp: Type) =
if (bs == null || tp.stableHash) tp.hash else tp.computeHash(bs)

def identityHash(bs: Binders) = avoidSpecialHashes(System.identityHashCode(this))

protected def finishHash(seed: Int, arity: Int, tp: Type): Int = {
val elemHash = tp.hash
protected def finishHash(bs: Binders, seed: Int, arity: Int, tp: Type): Int = {
val elemHash = typeHash(bs, tp)
if (elemHash == NotCached) return NotCached
finishHash(hashing.mix(seed, elemHash), arity + 1)
}

protected def finishHash(seed: Int, arity: Int, tp1: Type, tp2: Type): Int = {
val elemHash = tp1.hash
protected def finishHash(bs: Binders, seed: Int, arity: Int, tp1: Type, tp2: Type): Int = {
val elemHash = typeHash(bs, tp1)
if (elemHash == NotCached) return NotCached
finishHash(hashing.mix(seed, elemHash), arity + 1, tp2)
finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tp2)
}

protected def finishHash(seed: Int, arity: Int, tps: List[Type]): Int = {
protected def finishHash(bs: Binders, seed: Int, arity: Int, tps: List[Type]): Int = {
var h = seed
var xs = tps
var len = arity
while (xs.nonEmpty) {
val elemHash = xs.head.hash
val elemHash = typeHash(bs, xs.head)
if (elemHash == NotCached) return NotCached
h = hashing.mix(h, elemHash)
xs = xs.tail
Expand All @@ -61,33 +74,33 @@ trait Hashable {
finishHash(h, len)
}

protected def finishHash(seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = {
val elemHash = tp.hash
protected def finishHash(bs: Binders, seed: Int, arity: Int, tp: Type, tps: List[Type]): Int = {
val elemHash = typeHash(bs, tp)
if (elemHash == NotCached) return NotCached
finishHash(hashing.mix(seed, elemHash), arity + 1, tps)
finishHash(bs, hashing.mix(seed, elemHash), arity + 1, tps)
}


protected final def doHash(x: Any): Int =
finishHash(hashing.mix(hashSeed, x.hashCode), 1)

protected final def doHash(tp: Type): Int =
finishHash(hashSeed, 0, tp)

protected final def doHash(x1: Any, tp2: Type): Int =
finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2)
protected final def doHash(bs: Binders, tp: Type): Int =
finishHash(bs, hashSeed, 0, tp)

protected final def doHash(tp1: Type, tp2: Type): Int =
finishHash(hashSeed, 0, tp1, tp2)
protected final def doHash(bs: Binders, x1: Any, tp2: Type): Int =
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2)

protected final def doHash(x1: Any, tp2: Type, tp3: Type): Int =
finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3)
protected final def doHash(bs: Binders, tp1: Type, tp2: Type): Int =
finishHash(bs, hashSeed, 0, tp1, tp2)

protected final def doHash(tp1: Type, tps2: List[Type]): Int =
finishHash(hashSeed, 0, tp1, tps2)
protected final def doHash(bs: Binders, x1: Any, tp2: Type, tp3: Type): Int =
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tp3)

protected final def doHash(x1: Any, tp2: Type, tps3: List[Type]): Int =
finishHash(hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3)
protected final def doHash(bs: Binders, tp1: Type, tps2: List[Type]): Int =
finishHash(bs, hashSeed, 0, tp1, tps2)

protected final def doHash(bs: Binders, x1: Any, tp2: Type, tps3: List[Type]): Int =
finishHash(bs, hashing.mix(hashSeed, x1.hashCode), 1, tp2, tps3)

protected final def doHash(x1: Int, x2: Int): Int =
finishHash(hashing.mix(hashing.mix(hashSeed, x1), x2), 1)
Expand All @@ -96,7 +109,7 @@ trait Hashable {
if (elemHash == NotCached) NotCached
else avoidSpecialHashes(elemHash + delta)

private def avoidSpecialHashes(h: Int) =
protected def avoidSpecialHashes(h: Int) =
if (h == NotCached) NotCachedAlt
else if (h == HashUnknown) HashUnknownAlt
else h
Expand Down
40 changes: 20 additions & 20 deletions compiler/src/dotty/tools/dotc/core/Substituters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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) =>
Expand All @@ -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))
Expand All @@ -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
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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))
Expand All @@ -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))
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) = {
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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))
Expand Down
Loading