diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index af680510de14..f9ead676a504 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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) } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9e08d013b1b8..5abf8fe05880 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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. */ @@ -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. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5990b102dac0..6f39e041322d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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) + } + else ifNot + } + if (fail.isAmbiguous) failed else - tree + trySpecialCase(defn.ClassTagClass, synthesizedClassTag, + trySpecialCase(defn.QuotedTypeClass, synthesizedTypeTag, + trySpecialCase(defn.EqClass, synthesizedEq, failed))) } } diff --git a/tests/run/inlineForeach.scala b/tests/run/inlineForeach.scala index 1389ad6c4059..437e58cbfc5f 100644 --- a/tests/run/inlineForeach.scala +++ b/tests/run/inlineForeach.scala @@ -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(_)) @@ -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)) } } diff --git a/tests/run/patmatch-classtag.scala b/tests/run/patmatch-classtag.scala index f1f6645175e0..99e1ee3f05ae 100644 --- a/tests/run/patmatch-classtag.scala +++ b/tests/run/patmatch-classtag.scala @@ -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)