Skip to content

Commit 058729c

Browse files
committed
LazyRefs break cycles for unpickled types
Insert LazyRefs to break cycles for F-bounded types that are unpickled or read from Java signatures.
1 parent f87153b commit 058729c

File tree

7 files changed

+82
-31
lines changed

7 files changed

+82
-31
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ object SymDenotations {
147147
}
148148

149149
private def completeFrom(completer: LazyType)(implicit ctx: Context): Unit = {
150-
if (myFlags is Touched) throw new CyclicReference(this)
150+
if (myFlags is Touched) throw CyclicReference(this)
151151
myFlags |= Touched
152152

153153
// completions.println(s"completing ${this.debugString}")
@@ -1034,7 +1034,7 @@ object SymDenotations {
10341034
}
10351035

10361036
private def computeBases(implicit ctx: Context): Unit = {
1037-
if (myBaseClasses eq Nil) throw new CyclicReference(this)
1037+
if (myBaseClasses eq Nil) throw CyclicReference(this)
10381038
myBaseClasses = Nil
10391039
val seen = new mutable.BitSet
10401040
val locked = new mutable.BitSet
@@ -1294,7 +1294,7 @@ object SymDenotations {
12941294
basetp = computeBaseTypeRefOf(tp)
12951295
baseTypeRefCache.put(tp, basetp)
12961296
} else if (basetp == NoPrefix) {
1297-
throw new CyclicReference(this)
1297+
throw CyclicReference(this)
12981298
}
12991299
basetp
13001300
case _ =>

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ object Types {
12361236
if (ctx.underlyingRecursions < LogPendingUnderlyingThreshold)
12371237
op
12381238
else if (ctx.pendingUnderlying contains this)
1239-
throw new CyclicReference(symbol)
1239+
throw CyclicReference(symbol)
12401240
else
12411241
try {
12421242
ctx.pendingUnderlying += this
@@ -1487,7 +1487,7 @@ object Types {
14871487
unique(new CachedConstantType(value))
14881488
}
14891489

1490-
case class LazyRef(refFn: () => Type) extends UncachedProxyType with TermType {
1490+
case class LazyRef(refFn: () => Type) extends UncachedProxyType with ValueType {
14911491
lazy val ref = refFn()
14921492
override def underlying(implicit ctx: Context) = ref
14931493
override def toString = s"LazyRef($ref)"
@@ -2689,10 +2689,17 @@ object Types {
26892689
extends FatalTypeError(
26902690
s"""malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}""")
26912691

2692-
class CyclicReference(val denot: SymDenotation)
2692+
class CyclicReference private (val denot: SymDenotation)
26932693
extends FatalTypeError(s"cyclic reference involving $denot") {
26942694
def show(implicit ctx: Context) = s"cyclic reference involving ${denot.show}"
2695-
printStackTrace()
2695+
}
2696+
2697+
object CyclicReference {
2698+
def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = {
2699+
val ex = new CyclicReference(denot)
2700+
if (!(ctx.mode is typer.Mode.CheckCyclic)) ex.printStackTrace()
2701+
ex
2702+
}
26962703
}
26972704

26982705
class MergeError(msg: String) extends FatalTypeError(msg)

src/dotty/tools/dotc/core/pickling/ClassfileParser.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import java.lang.Integer.toHexString
1111
import scala.collection.{ mutable, immutable }
1212
import scala.collection.mutable.{ ListBuffer, ArrayBuffer }
1313
import scala.annotation.switch
14+
import typer.Checking.checkNonCyclic
1415
import io.AbstractFile
1516

1617
class ClassfileParser(
@@ -337,7 +338,11 @@ class ClassfileParser(
337338
val savedIndex = index
338339
try {
339340
index = start
340-
denot.info = sig2typeBounds(tparams, skiptvs = false)
341+
denot.info =
342+
checkNonCyclic( // we need the checkNonCyclic call to insert LazyRefs for F-bounded cycles
343+
denot.symbol,
344+
sig2typeBounds(tparams, skiptvs = false),
345+
reportErrors = false)
341346
} finally {
342347
index = savedIndex
343348
}

src/dotty/tools/dotc/core/pickling/UnPickler.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import printing.Texts._
1515
import printing.Printer
1616
import io.AbstractFile
1717
import util.common._
18+
import typer.Checking.checkNonCyclic
1819
import PickleBuffer._
1920
import scala.reflect.internal.pickling.PickleFormat._
2021
import Decorators._
@@ -516,7 +517,11 @@ class UnPickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClassRoot:
516517
denot setFlag Scala2x
517518
case denot =>
518519
val tp1 = depoly(tp, denot)
519-
denot.info = if (tag == ALIASsym) TypeAlias(tp1) else tp1
520+
denot.info =
521+
if (tag == ALIASsym) TypeAlias(tp1)
522+
else if (denot.isType) checkNonCyclic(denot.symbol, tp1, reportErrors = false)
523+
// we need the checkNonCyclic call to insert LazyRefs for F-bounded cycles
524+
else tp1
520525
if (denot.isConstructor) addConstructorTypeParams(denot)
521526
if (atEnd) {
522527
assert(!(denot is SuperAccessor), denot)

src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ object Checking {
5858
/** A type map which checks that the only cycles in a type are F-bounds
5959
* and that protects all F-bounded references by LazyRefs.
6060
*/
61-
class CheckNonCyclicMap(implicit ctx: Context) extends TypeMap {
61+
class CheckNonCyclicMap(sym: Symbol, reportErrors: Boolean)(implicit ctx: Context) extends TypeMap {
6262

6363
/** Are cycles allowed within nested refinedInfos of currently checked type? */
6464
private var nestedCycleOK = false
@@ -72,22 +72,32 @@ object Checking {
7272
*/
7373
var where: String = ""
7474

75+
/** The last type top-level type checked when a CyclicReference occurs. */
76+
var lastChecked: Type = NoType
77+
7578
/** Check info `tp` for cycles. Throw CyclicReference for illegal cycles,
7679
* break direct cycle with a LazyRef for legal, F-bounded cycles.
7780
*/
7881
def checkInfo(tp: Type): Type = tp match {
7982
case tp @ TypeBounds(lo, hi) =>
8083
if (lo eq hi)
8184
try tp.derivedTypeAlias(apply(lo))
82-
finally where = "alias"
85+
finally {
86+
where = "alias"
87+
lastChecked = lo
88+
}
8389
else {
84-
val lo1 = try apply(lo) finally where = "lower bound"
90+
val lo1 = try apply(lo) finally {
91+
where = "lower bound"
92+
lastChecked = lo
93+
}
8594
val saved = nestedCycleOK
8695
nestedCycleOK = true
8796
try tp.derivedTypeBounds(lo1, apply(hi))
8897
finally {
8998
nestedCycleOK = saved
9099
where = "upper bound"
100+
lastChecked = hi
91101
}
92102
}
93103
case _ =>
@@ -103,44 +113,66 @@ object Checking {
103113
finally cycleOK = saved
104114
case tp @ TypeRef(pre, name) =>
105115
try {
106-
// Check info of typeref recursively, marking the referred symbol
116+
// A prefix is interesting if it might contain (transitively) a reference
117+
// to symbol `sym` itself. We only check references with interesting
118+
// prefixes for cycles. This pruning is done in order not to force
119+
// global symbols when doing the cyclicity check.
120+
def isInteresting(prefix: Type): Boolean = prefix.stripTypeVar match {
121+
case NoPrefix => true
122+
case ThisType(cls) => sym.owner.isClass && cls.isContainedIn(sym.owner)
123+
case prefix: NamedType => !prefix.symbol.isStaticOwner && isInteresting(prefix.prefix)
124+
case SuperType(thistp, _) => isInteresting(thistp)
125+
case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2)
126+
case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2)
127+
case _ => false
128+
}
129+
// If prefix is interesting, check info of typeref recursively, marking the referred symbol
107130
// with NoCompleter. This provokes a CyclicReference when the symbol
108131
// is hit again. Without this precaution we could stackoverflow here.
109-
val info = tp.info
110-
val symInfo = tp.symbol.info
111-
if (tp.symbol.exists) tp.symbol.info = SymDenotations.NoCompleter
112-
try checkInfo(info)
113-
finally if (tp.symbol.exists) tp.symbol.info = symInfo
132+
if (isInteresting(pre)) {
133+
val info = tp.info
134+
val symInfo = tp.symbol.info
135+
if (tp.symbol.exists) tp.symbol.info = SymDenotations.NoCompleter
136+
try checkInfo(info)
137+
finally if (tp.symbol.exists) tp.symbol.info = symInfo
138+
}
114139
tp
115140
} catch {
116141
case ex: CyclicReference =>
117142
ctx.debuglog(i"cycle detected for $tp, $nestedCycleOK, $cycleOK")
118-
if (cycleOK) LazyRef(() => tp) else throw ex
143+
if (cycleOK) LazyRef(() => tp)
144+
else if (reportErrors) throw ex
145+
else tp
119146
}
120147
case _ => mapOver(tp)
121148
}
122149
}
123-
}
124-
125-
trait Checking {
126-
127-
import tpd._
128-
import Checking._
129150

130151
/** Check that `info` of symbol `sym` is not cyclic.
131152
* @pre sym is not yet initialized (i.e. its type is a Completer).
132153
* @return `info` where every legal F-bounded reference is proctected
133154
* by a `LazyRef`, or `ErrorType` if a cycle was detected and reported.
134155
*/
135-
def checkNonCyclic(sym: Symbol, info: TypeBounds)(implicit ctx: Context): Type = {
136-
val checker = new CheckNonCyclicMap
156+
def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(implicit ctx: Context): Type = {
157+
val checker = new CheckNonCyclicMap(sym, reportErrors)(ctx.withMode(Mode.CheckCyclic))
137158
try checker.checkInfo(info)
138159
catch {
139160
case ex: CyclicReference =>
140-
ctx.error(i"illegal cyclic reference: ${checker.where} $info of $sym refers back to the type itself", sym.pos)
141-
ErrorType
142-
}
161+
if (reportErrors) {
162+
ctx.error(i"illegal cyclic reference: ${checker.where} ${checker.lastChecked} of $sym refers back to the type itself", sym.pos)
163+
ErrorType
164+
}
165+
else info
166+
}
143167
}
168+
}
169+
170+
trait Checking {
171+
172+
import tpd._
173+
174+
def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type =
175+
Checking.checkNonCyclic(sym, info, reportErrors)
144176

145177
/** Check that Java statics and packages can only be used in selections.
146178
*/
@@ -252,6 +284,7 @@ trait Checking {
252284

253285
trait NoChecking extends Checking {
254286
import tpd._
287+
override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info
255288
override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
256289
override def checkBounds(args: List[tpd.Tree], poly: PolyType, pos: Position)(implicit ctx: Context): Unit = ()
257290
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()

src/dotty/tools/dotc/typer/Mode.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ object Mode {
3333

3434
val TypevarsMissContext = newMode(4, "TypevarsMissContext")
3535
val InSuperCall = newMode(5, "InSuperCall")
36+
val CheckCyclic = newMode(6, "CheckCyclic")
3637

3738
val PatternOrType = Pattern | Type
3839
}

src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,6 @@ class Namer { typer: Typer =>
679679
case _ => TypeAlias(abstractedRhsType, if (sym is Local) sym.variance else 0)
680680
}
681681
sym.info = NoCompleter
682-
checkNonCyclic(sym, unsafeInfo)
682+
checkNonCyclic(sym, unsafeInfo, reportErrors = true)
683683
}
684684
}

0 commit comments

Comments
 (0)