Skip to content

Commit 9f876b8

Browse files
authored
Merge pull request #4436 from dotty-staging/fix-specialized-implicits
Fix handling of special implicit searches for ClassTag, quoted.Type, Eq
2 parents 0a486e3 + 0e9f17e commit 9f876b8

File tree

5 files changed

+159
-100
lines changed

5 files changed

+159
-100
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 116 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,118 +1620,147 @@ object SymDenotations {
16201620

16211621
/** Compute tp.baseType(this) */
16221622
final def baseTypeOf(tp: Type)(implicit ctx: Context): Type = {
1623+
val btrCache = baseTypeCache
1624+
def inCache(tp: Type) = btrCache.get(tp) != null
1625+
def record(tp: CachedType, baseTp: Type) = {
1626+
if (Stats.monitored) {
1627+
Stats.record("basetype cache entries")
1628+
if (!baseTp.exists) Stats.record("basetype cache NoTypes")
1629+
}
1630+
btrCache.put(tp, baseTp)
1631+
}
16231632

1624-
def foldGlb(bt: Type, ps: List[Type]): Type = ps match {
1625-
case p :: ps1 => foldGlb(bt & baseTypeOf(p), ps1)
1626-
case _ => bt
1633+
def ensureAcyclic(baseTp: Type) = {
1634+
if (baseTp `eq` NoPrefix) throw CyclicReference(this)
1635+
baseTp
16271636
}
16281637

1629-
/** We cannot cache:
1630-
* - type variables which are uninstantiated or whose instances can
1631-
* change, depending on typerstate.
1632-
* - types where the underlying type is an ErasedValueType, because
1633-
* this underlying type will change after ElimErasedValueType,
1634-
* and this changes subtyping relations. As a shortcut, we do not
1635-
* cache ErasedValueType at all.
1636-
*/
1637-
def isCachable(tp: Type, btrCache: BaseTypeMap): Boolean = {
1638-
def inCache(tp: Type) = btrCache.containsKey(tp)
1638+
def recur(tp: Type): Type = try {
16391639
tp match {
1640-
case _: TypeErasure.ErasedValueType => false
1641-
case tp: TypeRef if tp.symbol.isClass => true
1642-
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
1643-
//case tp: TypeProxy => inCache(tp.underlying) // disabled, can re-enable insyead of last two lines for performance testing
1644-
case tp: TypeProxy => isCachable(tp.underlying, btrCache)
1645-
case tp: AndType => isCachable(tp.tp1, btrCache) && isCachable(tp.tp2, btrCache)
1646-
case tp: OrType => isCachable(tp.tp1, btrCache) && isCachable(tp.tp2, btrCache)
1647-
case _ => true
1640+
case tp: CachedType =>
1641+
val baseTp = btrCache.get(tp)
1642+
if (baseTp != null) return ensureAcyclic(baseTp)
1643+
case _ =>
16481644
}
1649-
}
1650-
1651-
def computeBaseTypeOf(tp: Type): Type = {
16521645
if (Stats.monitored) {
16531646
Stats.record("computeBaseType, total")
16541647
Stats.record(s"computeBaseType, ${tp.getClass}")
16551648
}
1656-
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
1657-
symbol.typeRef
1658-
else tp match {
1649+
tp match {
16591650
case tp @ TypeRef(prefix, _) =>
1660-
val subsym = tp.symbol
1661-
if (subsym eq symbol) tp
1662-
else subsym.denot match {
1663-
case clsd: ClassDenotation =>
1664-
val owner = clsd.owner
1665-
val isOwnThis = prefix match {
1666-
case prefix: ThisType => prefix.cls eq owner
1667-
case NoPrefix => true
1668-
case _ => false
1669-
}
1670-
if (isOwnThis)
1671-
if (clsd.baseClassSet.contains(symbol)) foldGlb(NoType, clsd.classParents)
1672-
else NoType
1673-
else
1674-
baseTypeOf(clsd.typeRef).asSeenFrom(prefix, owner)
1675-
case _ =>
1676-
baseTypeOf(tp.superType)
1651+
1652+
def foldGlb(bt: Type, ps: List[Type]): Type = ps match {
1653+
case p :: ps1 => foldGlb(bt & recur(p), ps1)
1654+
case _ => bt
16771655
}
1656+
1657+
def computeTypeRef = {
1658+
btrCache.put(tp, NoPrefix)
1659+
tp.symbol.denot match {
1660+
case clsd: ClassDenotation =>
1661+
def isOwnThis = prefix match {
1662+
case prefix: ThisType => prefix.cls `eq` clsd.owner
1663+
case NoPrefix => true
1664+
case _ => false
1665+
}
1666+
val baseTp =
1667+
if (tp.symbol eq symbol)
1668+
tp
1669+
else if (isOwnThis)
1670+
if (clsd.baseClassSet.contains(symbol))
1671+
if (symbol.isStatic && symbol.typeParams.isEmpty) symbol.typeRef
1672+
else foldGlb(NoType, clsd.classParents)
1673+
else NoType
1674+
else
1675+
recur(clsd.typeRef).asSeenFrom(prefix, clsd.owner)
1676+
record(tp, baseTp)
1677+
baseTp
1678+
case _ =>
1679+
val superTp = tp.superType
1680+
val baseTp = recur(superTp)
1681+
if (inCache(superTp) && tp.symbol.maybeOwner.isType)
1682+
record(tp, baseTp) // typeref cannot be a GADT, so cache is stable
1683+
else
1684+
btrCache.remove(tp)
1685+
baseTp
1686+
}
1687+
}
1688+
computeTypeRef
1689+
16781690
case tp @ AppliedType(tycon, args) =>
1679-
val subsym = tycon.typeSymbol
1680-
if (subsym eq symbol) tp
1681-
else (tycon.typeParams: @unchecked) match {
1682-
case LambdaParam(_, _) :: _ =>
1683-
baseTypeOf(tp.superType)
1684-
case tparams: List[Symbol @unchecked] =>
1685-
baseTypeOf(tycon).subst(tparams, args)
1691+
1692+
def computeApplied = {
1693+
btrCache.put(tp, NoPrefix)
1694+
val baseTp =
1695+
if (tycon.typeSymbol eq symbol) tp
1696+
else (tycon.typeParams: @unchecked) match {
1697+
case LambdaParam(_, _) :: _ =>
1698+
recur(tp.superType)
1699+
case tparams: List[Symbol @unchecked] =>
1700+
recur(tycon).subst(tparams, args)
1701+
}
1702+
record(tp, baseTp)
1703+
baseTp
16861704
}
1705+
computeApplied
1706+
1707+
case tp: TypeParamRef => // uncachable, since baseType depends on context bounds
1708+
recur(ctx.typeComparer.bounds(tp).hi)
16871709
case tp: TypeProxy =>
1688-
baseTypeOf(tp.superType)
1689-
case AndType(tp1, tp2) =>
1690-
baseTypeOf(tp1) & baseTypeOf(tp2) match {
1691-
case AndType(tp1a, tp2a) if (tp1a eq tp1) && (tp2a eq tp2) => tp
1692-
case res => res
1710+
1711+
def computeTypeProxy = {
1712+
val superTp = tp.superType
1713+
val baseTp = recur(superTp)
1714+
tp match {
1715+
case tp: CachedType if baseTp.exists && inCache(superTp) =>
1716+
// Note: This also works for TypeVars: If they are not instantiated, their supertype
1717+
// is a TypeParamRef, which is never cached. So uninstantiated TypeVars are not cached either.
1718+
record(tp, baseTp)
1719+
case _ =>
1720+
}
1721+
baseTp
16931722
}
1694-
case OrType(tp1, tp2) =>
1695-
baseTypeOf(tp1) | baseTypeOf(tp2) match {
1696-
case OrType(tp1a, tp2a) if (tp1a eq tp1) && (tp2a eq tp2) => tp
1697-
case res => res
1723+
computeTypeProxy
1724+
1725+
case tp: AndOrType =>
1726+
1727+
def computeAndOrType = {
1728+
val tp1 = tp.tp1
1729+
val tp2 = tp.tp2
1730+
val baseTp =
1731+
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
1732+
symbol.typeRef
1733+
else {
1734+
val baseTp1 = recur(tp1)
1735+
val baseTp2 = recur(tp2)
1736+
val combined = if (tp.isAnd) baseTp1 & baseTp2 else baseTp1 | baseTp2
1737+
combined match {
1738+
case combined: AndOrType
1739+
if (combined.tp1 eq tp1) && (combined.tp2 eq tp2) && (combined.isAnd == tp.isAnd) => tp
1740+
case _ => combined
1741+
}
1742+
}
1743+
if (baseTp.exists && inCache(tp1) && inCache(tp2)) record(tp, baseTp)
1744+
baseTp
16981745
}
1746+
computeAndOrType
1747+
16991748
case JavaArrayType(_) if symbol == defn.ObjectClass =>
17001749
this.typeRef
17011750
case _ =>
17021751
NoType
17031752
}
17041753
}
1754+
catch {
1755+
case ex: Throwable =>
1756+
btrCache.remove(tp)
1757+
throw ex
1758+
}
1759+
17051760

17061761
/*>|>*/ trace.onDebug(s"$tp.baseType($this)") /*<|<*/ {
17071762
Stats.record("baseTypeOf")
1708-
tp.stripTypeVar match {
1709-
case tp: CachedType =>
1710-
val btrCache = baseTypeCache
1711-
try {
1712-
var basetp = btrCache get tp
1713-
if (basetp == null) {
1714-
btrCache.put(tp, NoPrefix)
1715-
basetp = computeBaseTypeOf(tp)
1716-
if (!basetp.exists) Stats.record("base type miss")
1717-
if (isCachable(tp, btrCache)) {
1718-
if (basetp.exists) Stats.record("cached base type hit")
1719-
else Stats.record("cached base type miss")
1720-
btrCache.put(tp, basetp)
1721-
}
1722-
else btrCache.remove(tp)
1723-
} else if (basetp `eq` NoPrefix)
1724-
throw CyclicReference(this)
1725-
basetp
1726-
}
1727-
catch {
1728-
case ex: Throwable =>
1729-
btrCache.put(tp, null)
1730-
throw ex
1731-
}
1732-
case tp =>
1733-
computeBaseTypeOf(tp)
1734-
}
1763+
recur(tp)
17351764
}
17361765
}
17371766

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,7 +2441,14 @@ object Types {
24412441

24422442
// --- AndType/OrType ---------------------------------------------------------------
24432443

2444-
abstract case class AndType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType {
2444+
abstract class AndOrType extends CachedGroundType with ValueType {
2445+
def isAnd: Boolean
2446+
def tp1: Type
2447+
def tp2: Type
2448+
}
2449+
2450+
abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType {
2451+
def isAnd = true
24452452
private[this] var myBaseClassesPeriod: Period = Nowhere
24462453
private[this] var myBaseClasses: List[ClassSymbol] = _
24472454
/** Base classes of are the merge of the operand base classes. */
@@ -2505,7 +2512,8 @@ object Types {
25052512
if (checkValid) apply(tp1, tp2) else unchecked(tp1, tp2)
25062513
}
25072514

2508-
abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType {
2515+
abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType {
2516+
def isAnd = false
25092517
private[this] var myBaseClassesPeriod: Period = Nowhere
25102518
private[this] var myBaseClasses: List[ClassSymbol] = _
25112519
/** Base classes of are the intersection of the operand base classes. */

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -650,17 +650,20 @@ trait Implicits { self: Typer =>
650650
ref(lazyImplicit))
651651
else
652652
arg
653-
case fail @ SearchFailure(tree) =>
654-
if (fail.isAmbiguous)
655-
tree
656-
else if (formalValue.isRef(defn.ClassTagClass))
657-
synthesizedClassTag(formalValue).orElse(tree)
658-
else if (formalValue.isRef(defn.QuotedTypeClass))
659-
synthesizedTypeTag(formalValue).orElse(tree)
660-
else if (formalValue.isRef(defn.EqClass))
661-
synthesizedEq(formalValue).orElse(tree)
653+
case fail @ SearchFailure(failed) =>
654+
def trySpecialCase(cls: ClassSymbol, handler: Type => Tree, ifNot: => Tree) = {
655+
val base = formalValue.baseType(cls)
656+
if (base <:< formalValue) {
657+
// With the subtype test we enforce that the searched type `formalValue` is of the right form
658+
handler(base).orElse(ifNot)
659+
}
660+
else ifNot
661+
}
662+
if (fail.isAmbiguous) failed
662663
else
663-
tree
664+
trySpecialCase(defn.ClassTagClass, synthesizedClassTag,
665+
trySpecialCase(defn.QuotedTypeClass, synthesizedTypeTag,
666+
trySpecialCase(defn.EqClass, synthesizedEq, failed)))
664667
}
665668
}
666669

tests/run/inlineForeach.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ object Test {
3535
zs
3636
}
3737

38+
implicit class intArrayOps(arr: Array[Int]) {
39+
inline def foreach(inline op: Int => Unit): Unit = {
40+
var i = 0
41+
while (i < arr.length) {
42+
op(arr(i))
43+
i += 1
44+
}
45+
}
46+
}
47+
48+
def sum(ints: Array[Int]): Int = {
49+
var t = 0
50+
for (n <- ints) t += n
51+
t
52+
}
53+
3854
def main(args: Array[String]) = {
3955
1.until(10).foreach(i => println(i))
4056
1.until(10).foreach(println(_))
@@ -44,5 +60,8 @@ object Test {
4460
for (k1 <- 1 to 10)
4561
for (k2 <- 1 to 10)
4662
println(s"$k1")
63+
64+
val xs = Array(1, 2, 3, 4)
65+
assert(sum(xs) == 10, sum(xs))
4766
}
4867
}

tests/run/patmatch-classtag.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ object dotc {
1818
object Impl extends API {
1919
type CaseDef = dotc.CaseDef
2020

21-
val tagForCaseDef: ClassTag[dotc.CaseDef] = implicitly[ClassTag[dotc.CaseDef]]
21+
val tagForCaseDef: ClassTag[dotc.CaseDef] = implicitly
2222

2323
object CaseDef extends CaseDefCompanion {
2424
def apply(str: String): CaseDef = dotc.CaseDef(str)

0 commit comments

Comments
 (0)