Skip to content

Fix handling of special implicit searches for ClassTag, quoted.Type, Eq #4436

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 5 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 116 additions & 87 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1621,118 +1621,147 @@ object SymDenotations {

/** Compute tp.baseType(this) */
final def baseTypeOf(tp: Type)(implicit ctx: Context): Type = {
val btrCache = baseTypeCache
def inCache(tp: Type) = btrCache.get(tp) != null
def record(tp: CachedType, baseTp: Type) = {
if (Stats.monitored) {
Stats.record("basetype cache entries")
if (!baseTp.exists) Stats.record("basetype cache NoTypes")
}
btrCache.put(tp, baseTp)
}

def foldGlb(bt: Type, ps: List[Type]): Type = ps match {
case p :: ps1 => foldGlb(bt & baseTypeOf(p), ps1)
case _ => bt
def ensureAcyclic(baseTp: Type) = {
if (baseTp `eq` NoPrefix) throw CyclicReference(this)
baseTp
}

/** We cannot cache:
* - type variables which are uninstantiated or whose instances can
* change, depending on typerstate.
* - types where the underlying type is an ErasedValueType, because
* this underlying type will change after ElimErasedValueType,
* and this changes subtyping relations. As a shortcut, we do not
* cache ErasedValueType at all.
*/
def isCachable(tp: Type, btrCache: BaseTypeMap): Boolean = {
def inCache(tp: Type) = btrCache.containsKey(tp)
def recur(tp: Type): Type = try {
tp match {
case _: TypeErasure.ErasedValueType => false
case tp: TypeRef if tp.symbol.isClass => true
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
//case tp: TypeProxy => inCache(tp.underlying) // disabled, can re-enable insyead of last two lines for performance testing
case tp: TypeProxy => isCachable(tp.underlying, btrCache)
case tp: AndType => isCachable(tp.tp1, btrCache) && isCachable(tp.tp2, btrCache)
case tp: OrType => isCachable(tp.tp1, btrCache) && isCachable(tp.tp2, btrCache)
case _ => true
case tp: CachedType =>
val baseTp = btrCache.get(tp)
if (baseTp != null) return ensureAcyclic(baseTp)
case _ =>
}
}

def computeBaseTypeOf(tp: Type): Type = {
if (Stats.monitored) {
Stats.record("computeBaseType, total")
Stats.record(s"computeBaseType, ${tp.getClass}")
}
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
symbol.typeRef
else tp match {
tp match {
case tp @ TypeRef(prefix, _) =>
val subsym = tp.symbol
if (subsym eq symbol) tp
else subsym.denot match {
case clsd: ClassDenotation =>
val owner = clsd.owner
val isOwnThis = prefix match {
case prefix: ThisType => prefix.cls eq owner
case NoPrefix => true
case _ => false
}
if (isOwnThis)
if (clsd.baseClassSet.contains(symbol)) foldGlb(NoType, clsd.classParents)
else NoType
else
baseTypeOf(clsd.typeRef).asSeenFrom(prefix, owner)
case _ =>
baseTypeOf(tp.superType)

def foldGlb(bt: Type, ps: List[Type]): Type = ps match {
case p :: ps1 => foldGlb(bt & recur(p), ps1)
case _ => bt
}

def computeTypeRef = {
btrCache.put(tp, NoPrefix)
tp.symbol.denot match {
case clsd: ClassDenotation =>
def isOwnThis = prefix match {
case prefix: ThisType => prefix.cls `eq` clsd.owner
case NoPrefix => true
case _ => false
}
val baseTp =
if (tp.symbol eq symbol)
tp
else if (isOwnThis)
if (clsd.baseClassSet.contains(symbol))
if (symbol.isStatic && symbol.typeParams.isEmpty) symbol.typeRef
else foldGlb(NoType, clsd.classParents)
else NoType
else
recur(clsd.typeRef).asSeenFrom(prefix, clsd.owner)
record(tp, baseTp)
baseTp
case _ =>
val superTp = tp.superType
val baseTp = recur(superTp)
if (inCache(superTp) && tp.symbol.maybeOwner.isType)
record(tp, baseTp) // typeref cannot be a GADT, so cache is stable
else
btrCache.remove(tp)
baseTp
}
}
computeTypeRef

case tp @ AppliedType(tycon, args) =>
val subsym = tycon.typeSymbol
if (subsym eq symbol) tp
else (tycon.typeParams: @unchecked) match {
case LambdaParam(_, _) :: _ =>
baseTypeOf(tp.superType)
case tparams: List[Symbol @unchecked] =>
baseTypeOf(tycon).subst(tparams, args)

def computeApplied = {
btrCache.put(tp, NoPrefix)
val baseTp =
if (tycon.typeSymbol eq symbol) tp
else (tycon.typeParams: @unchecked) match {
case LambdaParam(_, _) :: _ =>
recur(tp.superType)
case tparams: List[Symbol @unchecked] =>
recur(tycon).subst(tparams, args)
}
record(tp, baseTp)
baseTp
}
computeApplied

case tp: TypeParamRef => // uncachable, since baseType depends on context bounds
recur(ctx.typeComparer.bounds(tp).hi)
case tp: TypeProxy =>
baseTypeOf(tp.superType)
case AndType(tp1, tp2) =>
baseTypeOf(tp1) & baseTypeOf(tp2) match {
case AndType(tp1a, tp2a) if (tp1a eq tp1) && (tp2a eq tp2) => tp
case res => res

def computeTypeProxy = {
val superTp = tp.superType
val baseTp = recur(superTp)
tp match {
case tp: CachedType if baseTp.exists && inCache(superTp) =>
// Note: This also works for TypeVars: If they are not instantiated, their supertype
// is a TypeParamRef, which is never cached. So uninstantiated TypeVars are not cached either.
record(tp, baseTp)
case _ =>
}
baseTp
}
case OrType(tp1, tp2) =>
baseTypeOf(tp1) | baseTypeOf(tp2) match {
case OrType(tp1a, tp2a) if (tp1a eq tp1) && (tp2a eq tp2) => tp
case res => res
computeTypeProxy

case tp: AndOrType =>

def computeAndOrType = {
val tp1 = tp.tp1
val tp2 = tp.tp2
val baseTp =
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
symbol.typeRef
else {
val baseTp1 = recur(tp1)
val baseTp2 = recur(tp2)
val combined = if (tp.isAnd) baseTp1 & baseTp2 else baseTp1 | baseTp2
combined match {
case combined: AndOrType
if (combined.tp1 eq tp1) && (combined.tp2 eq tp2) && (combined.isAnd == tp.isAnd) => tp
case _ => combined
}
}
if (baseTp.exists && inCache(tp1) && inCache(tp2)) record(tp, baseTp)
baseTp
}
computeAndOrType

case JavaArrayType(_) if symbol == defn.ObjectClass =>
this.typeRef
case _ =>
NoType
}
}
catch {
case ex: Throwable =>
btrCache.remove(tp)
throw ex
}


/*>|>*/ trace.onDebug(s"$tp.baseType($this)") /*<|<*/ {
Stats.record("baseTypeOf")
tp.stripTypeVar match {
case tp: CachedType =>
val btrCache = baseTypeCache
try {
var basetp = btrCache get tp
if (basetp == null) {
btrCache.put(tp, NoPrefix)
basetp = computeBaseTypeOf(tp)
if (!basetp.exists) Stats.record("base type miss")
if (isCachable(tp, btrCache)) {
if (basetp.exists) Stats.record("cached base type hit")
else Stats.record("cached base type miss")
btrCache.put(tp, basetp)
}
else btrCache.remove(tp)
} else if (basetp `eq` NoPrefix)
throw CyclicReference(this)
basetp
}
catch {
case ex: Throwable =>
btrCache.put(tp, null)
throw ex
}
case tp =>
computeBaseTypeOf(tp)
}
recur(tp)
}
}

Expand Down
12 changes: 10 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2438,7 +2438,14 @@ object Types {

// --- AndType/OrType ---------------------------------------------------------------

abstract case class AndType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType {
abstract class AndOrType extends CachedGroundType with ValueType {
def isAnd: Boolean
def tp1: Type
def tp2: Type
}

abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType {
def isAnd = true
private[this] var myBaseClassesPeriod: Period = Nowhere
private[this] var myBaseClasses: List[ClassSymbol] = _
/** Base classes of are the merge of the operand base classes. */
Expand Down Expand Up @@ -2502,7 +2509,8 @@ object Types {
if (checkValid) apply(tp1, tp2) else unchecked(tp1, tp2)
}

abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType {
abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType {
def isAnd = false
private[this] var myBaseClassesPeriod: Period = Nowhere
private[this] var myBaseClasses: List[ClassSymbol] = _
/** Base classes of are the intersection of the operand base classes. */
Expand Down
23 changes: 13 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -650,17 +650,20 @@ trait Implicits { self: Typer =>
ref(lazyImplicit))
else
arg
case fail @ SearchFailure(tree) =>
if (fail.isAmbiguous)
tree
else if (formalValue.isRef(defn.ClassTagClass))
synthesizedClassTag(formalValue).orElse(tree)
else if (formalValue.isRef(defn.QuotedTypeClass))
synthesizedTypeTag(formalValue).orElse(tree)
else if (formalValue.isRef(defn.EqClass))
synthesizedEq(formalValue).orElse(tree)
case fail @ SearchFailure(failed) =>
def trySpecialCase(cls: ClassSymbol, handler: Type => Tree, ifNot: => Tree) = {
val base = formalValue.baseType(cls)
if (base <:< formalValue) {
// With the subtype test we enforce that the searched type `formalValue` is of the right form
handler(base).orElse(ifNot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In synthesizedClassTag there's a call to fullyDefinedType, I wonder if we should move this around here to do this consistently for all handlers.

}
else ifNot
}
if (fail.isAmbiguous) failed
else
tree
trySpecialCase(defn.ClassTagClass, synthesizedClassTag,
trySpecialCase(defn.QuotedTypeClass, synthesizedTypeTag,
trySpecialCase(defn.EqClass, synthesizedEq, failed)))
}
}

Expand Down
19 changes: 19 additions & 0 deletions tests/run/inlineForeach.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ object Test {
zs
}

implicit class intArrayOps(arr: Array[Int]) {
inline def foreach(inline op: Int => Unit): Unit = {
var i = 0
while (i < arr.length) {
op(arr(i))
i += 1
}
}
}

def sum(ints: Array[Int]): Int = {
var t = 0
for (n <- ints) t += n
t
}

def main(args: Array[String]) = {
1.until(10).foreach(i => println(i))
1.until(10).foreach(println(_))
Expand All @@ -44,5 +60,8 @@ object Test {
for (k1 <- 1 to 10)
for (k2 <- 1 to 10)
println(s"$k1")

val xs = Array(1, 2, 3, 4)
assert(sum(xs) == 10, sum(xs))
}
}
2 changes: 1 addition & 1 deletion tests/run/patmatch-classtag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object dotc {
object Impl extends API {
type CaseDef = dotc.CaseDef

val tagForCaseDef: ClassTag[dotc.CaseDef] = implicitly[ClassTag[dotc.CaseDef]]
val tagForCaseDef: ClassTag[dotc.CaseDef] = implicitly

object CaseDef extends CaseDefCompanion {
def apply(str: String): CaseDef = dotc.CaseDef(str)
Expand Down