diff options
author | Martin Odersky <odersky@gmail.com> | 2014-08-08 16:12:01 +0200 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2014-08-08 18:32:33 +0200 |
commit | 058729ceac3354a2cc34490b528e76afb09ee0ce (patch) | |
tree | 2b98e78b607c7f0438ebd2eac8b68f5a72b46a04 /src/dotty/tools/dotc/typer/Checking.scala | |
parent | f87153bc5d74f66e2fcf22dc7282da31813430da (diff) | |
download | dotty-058729ceac3354a2cc34490b528e76afb09ee0ce.tar.gz dotty-058729ceac3354a2cc34490b528e76afb09ee0ce.tar.bz2 dotty-058729ceac3354a2cc34490b528e76afb09ee0ce.zip |
LazyRefs break cycles for unpickled types
Insert LazyRefs to break cycles for F-bounded types that
are unpickled or read from Java signatures.
Diffstat (limited to 'src/dotty/tools/dotc/typer/Checking.scala')
-rw-r--r-- | src/dotty/tools/dotc/typer/Checking.scala | 75 |
1 files changed, 54 insertions, 21 deletions
diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index cd2e9b55f..eb202cc15 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -58,7 +58,7 @@ object Checking { /** A type map which checks that the only cycles in a type are F-bounds * and that protects all F-bounded references by LazyRefs. */ - class CheckNonCyclicMap(implicit ctx: Context) extends TypeMap { + class CheckNonCyclicMap(sym: Symbol, reportErrors: Boolean)(implicit ctx: Context) extends TypeMap { /** Are cycles allowed within nested refinedInfos of currently checked type? */ private var nestedCycleOK = false @@ -72,6 +72,9 @@ object Checking { */ var where: String = "" + /** The last type top-level type checked when a CyclicReference occurs. */ + var lastChecked: Type = NoType + /** Check info `tp` for cycles. Throw CyclicReference for illegal cycles, * break direct cycle with a LazyRef for legal, F-bounded cycles. */ @@ -79,15 +82,22 @@ object Checking { case tp @ TypeBounds(lo, hi) => if (lo eq hi) try tp.derivedTypeAlias(apply(lo)) - finally where = "alias" + finally { + where = "alias" + lastChecked = lo + } else { - val lo1 = try apply(lo) finally where = "lower bound" + val lo1 = try apply(lo) finally { + where = "lower bound" + lastChecked = lo + } val saved = nestedCycleOK nestedCycleOK = true try tp.derivedTypeBounds(lo1, apply(hi)) finally { nestedCycleOK = saved where = "upper bound" + lastChecked = hi } } case _ => @@ -103,44 +113,66 @@ object Checking { finally cycleOK = saved case tp @ TypeRef(pre, name) => try { - // Check info of typeref recursively, marking the referred symbol + // A prefix is interesting if it might contain (transitively) a reference + // to symbol `sym` itself. We only check references with interesting + // prefixes for cycles. This pruning is done in order not to force + // global symbols when doing the cyclicity check. + def isInteresting(prefix: Type): Boolean = prefix.stripTypeVar match { + case NoPrefix => true + case ThisType(cls) => sym.owner.isClass && cls.isContainedIn(sym.owner) + case prefix: NamedType => !prefix.symbol.isStaticOwner && isInteresting(prefix.prefix) + case SuperType(thistp, _) => isInteresting(thistp) + case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2) + case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2) + case _ => false + } + // If prefix is interesting, check info of typeref recursively, marking the referred symbol // with NoCompleter. This provokes a CyclicReference when the symbol // is hit again. Without this precaution we could stackoverflow here. - val info = tp.info - val symInfo = tp.symbol.info - if (tp.symbol.exists) tp.symbol.info = SymDenotations.NoCompleter - try checkInfo(info) - finally if (tp.symbol.exists) tp.symbol.info = symInfo + if (isInteresting(pre)) { + val info = tp.info + val symInfo = tp.symbol.info + if (tp.symbol.exists) tp.symbol.info = SymDenotations.NoCompleter + try checkInfo(info) + finally if (tp.symbol.exists) tp.symbol.info = symInfo + } tp } catch { case ex: CyclicReference => ctx.debuglog(i"cycle detected for $tp, $nestedCycleOK, $cycleOK") - if (cycleOK) LazyRef(() => tp) else throw ex + if (cycleOK) LazyRef(() => tp) + else if (reportErrors) throw ex + else tp } case _ => mapOver(tp) } } -} - -trait Checking { - - import tpd._ - import Checking._ /** Check that `info` of symbol `sym` is not cyclic. * @pre sym is not yet initialized (i.e. its type is a Completer). * @return `info` where every legal F-bounded reference is proctected * by a `LazyRef`, or `ErrorType` if a cycle was detected and reported. */ - def checkNonCyclic(sym: Symbol, info: TypeBounds)(implicit ctx: Context): Type = { - val checker = new CheckNonCyclicMap + def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(implicit ctx: Context): Type = { + val checker = new CheckNonCyclicMap(sym, reportErrors)(ctx.withMode(Mode.CheckCyclic)) try checker.checkInfo(info) catch { case ex: CyclicReference => - ctx.error(i"illegal cyclic reference: ${checker.where} $info of $sym refers back to the type itself", sym.pos) - ErrorType - } + if (reportErrors) { + ctx.error(i"illegal cyclic reference: ${checker.where} ${checker.lastChecked} of $sym refers back to the type itself", sym.pos) + ErrorType + } + else info + } } +} + +trait Checking { + + import tpd._ + + def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = + Checking.checkNonCyclic(sym, info, reportErrors) /** Check that Java statics and packages can only be used in selections. */ @@ -252,6 +284,7 @@ trait Checking { trait NoChecking extends Checking { import tpd._ + override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree override def checkBounds(args: List[tpd.Tree], poly: PolyType, pos: Position)(implicit ctx: Context): Unit = () override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () |