diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 12cfc5e9e0d8..f7df30b404c6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -13,6 +13,7 @@ import collection.mutable.ListBuffer import parsing.Tokens.Token import printing.Printer import util.{Stats, Attachment, Property, DotClass} +import config.Config import annotation.unchecked.uncheckedVariance import language.implicitConversions @@ -113,10 +114,30 @@ object Trees { * type. (Overridden by empty trees) */ def withType(tpe: Type)(implicit ctx: Context): ThisTree[Type] = { - if (tpe.isInstanceOf[ErrorType]) assert(ctx.reporter.errorsReported) + if (tpe.isInstanceOf[ErrorType]) + assert(ctx.reporter.errorsReported) + else if (Config.checkTreesConsistent) + checkChildrenTyped(productIterator) withTypeUnchecked(tpe) } + /** Check that typed trees don't refer to untyped ones, except if + * - the parent tree is an import, or + * - the child tree is an identifier, or + * - errors were reported + */ + private def checkChildrenTyped(it: Iterator[Any])(implicit ctx: Context): Unit = + if (!this.isInstanceOf[Import[_]]) + while (it.hasNext) + it.next match { + case x: Ident[_] => // untyped idents are used in a number of places in typed trees + case x: Tree[_] => + assert(x.hasType || ctx.reporter.errorsReported, + s"$this has untyped child $x") + case xs: List[_] => checkChildrenTyped(xs.iterator) + case _ => + } + def withTypeUnchecked(tpe: Type): ThisTree[Type] = { val tree = (if (myTpe == null || @@ -1177,6 +1198,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) + case _ if ctx.reporter.errorsReported => + tree } def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] = @@ -1282,6 +1305,9 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) + case _ if ctx.reporter.errorsReported => + // in case of errors it may be that typed trees point to untyped ones. + x } } } diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 13dd45037813..89b9a714b268 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -72,6 +72,9 @@ object Config { /** Check positions for consistency after parsing */ final val checkPositions = true + /** Check that typed trees don't point to untyped ones */ + final val checkTreesConsistent = false + /** Show subtype traces for all deep subtype recursions */ final val traceDeepSubTypeRecursions = false @@ -163,4 +166,7 @@ object Config { * when findMemberLimit is set. */ final val PendingFindMemberLimit = LogPendingFindMemberThreshold * 4 + + /** When in IDE, turn StaleSymbol errors into warnings instead of crashing */ + final val ignoreStaleInIDE = true } diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 46c8c0a6a03b..f27ede8153db 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -722,7 +722,7 @@ object Denotations { * if denotation is no longer valid. */ private def bringForward()(implicit ctx: Context): SingleDenotation = this match { - case denot: SymDenotation if ctx.stillValid(denot) => + case denot: SymDenotation if ctx.stillValid(denot) || ctx.acceptStale(denot) => assert(ctx.runId > validFor.runId || ctx.settings.YtestPickler.value, // mixing test pickler with debug printing can travel back in time s"denotation $denot invalid in run ${ctx.runId}. ValidFor: $validFor") var d: SingleDenotation = denot @@ -918,13 +918,15 @@ object Denotations { old.nextInRun = this } - def staleSymbolError(implicit ctx: Context) = { + def staleSymbolError(implicit ctx: Context) = + throw new StaleSymbol(staleSymbolMsg) + + def staleSymbolMsg(implicit ctx: Context): String = { def ownerMsg = this match { case denot: SymDenotation => s"in ${denot.owner}" case _ => "" } - def msg = s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${myValidFor}, is referred to in run ${ctx.period}" - throw new StaleSymbol(msg) + s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${myValidFor}, is referred to in run ${ctx.period}" } /** The period (interval of phases) for which there exists diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 8437dfca5436..95a4684a4b9c 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -90,4 +90,7 @@ object Mode { * In this case, identifiers should never be imported. */ val InPackageClauseName = newMode(18, "InPackageClauseName") + + /** We are in the IDE */ + val Interactive = newMode(20, "Interactive") } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 894c8e37a987..6fa4abbcf809 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -99,6 +99,13 @@ trait SymDenotations { this: Context => explain("denotation is not a SymDenotation") } } + + /** Configurable: Accept stale symbol with warning if in IDE */ + def acceptStale(denot: SingleDenotation): Boolean = + mode.is(Mode.Interactive) && Config.ignoreStaleInIDE && { + ctx.echo(denot.staleSymbolMsg) + true + } } object SymDenotations { diff --git a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala index a2a0047bfb2d..674e712d6499 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Interactive.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Interactive.scala @@ -131,19 +131,20 @@ object Interactive { val buf = new mutable.ListBuffer[SourceTree] trees foreach { case SourceTree(topTree, source) => - (new TreeTraverser { - override def traverse(tree: Tree)(implicit ctx: Context) = { + (new untpd.TreeTraverser { + override def traverse(tree: untpd.Tree)(implicit ctx: Context) = { tree match { - case _: Inlined => + case _: untpd.Inlined => // Skip inlined trees - case tree: NameTree - if tree.symbol.exists - && !tree.symbol.is(Synthetic) - && tree.pos.exists - && !tree.pos.isZeroExtent - && (includeReferences || isDefinition(tree)) - && treePredicate(tree) => - buf += SourceTree(tree, source) + case utree: untpd.NameTree if tree.hasType => + val tree = utree.asInstanceOf[tpd.NameTree] + if (tree.symbol.exists + && !tree.symbol.is(Synthetic) + && tree.pos.exists + && !tree.pos.isZeroExtent + && (includeReferences || isDefinition(tree)) + && treePredicate(tree)) + buf += SourceTree(tree, source) case _ => } traverseChildren(tree) diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala index 5f049aadb970..6822699a4f9c 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala @@ -5,6 +5,7 @@ package interactive import core._ import Phases._ import typer._ +import Contexts._ class InteractiveCompiler extends Compiler { // TODO: Figure out what phases should be run in IDEs diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index d47f165c93a7..c19bc0556f8a 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -33,7 +33,7 @@ class InteractiveDriver(settings: List[String]) extends Driver { override def sourcesRequired = false private val myInitCtx: Context = { - val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions) + val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive) rootCtx.setSetting(rootCtx.settings.YretainTrees, true) val ctx = setup(settings.toArray, rootCtx)._2 ctx.initialize()(ctx) diff --git a/compiler/src/dotty/tools/dotc/util/Positions.scala b/compiler/src/dotty/tools/dotc/util/Positions.scala index 349933f5db7b..c98b02f06726 100644 --- a/compiler/src/dotty/tools/dotc/util/Positions.scala +++ b/compiler/src/dotty/tools/dotc/util/Positions.scala @@ -100,13 +100,18 @@ object Positions { /** A copy of this position with a different start */ def withStart(start: Int) = - fromOffsets(start, this.end, if (isSynthetic) SyntheticPointDelta else this.point - start) + if (exists) fromOffsets(start, this.end, if (isSynthetic) SyntheticPointDelta else this.point - start) + else this /** A copy of this position with a different end */ - def withEnd(end: Int) = fromOffsets(this.start, end, pointDelta) + def withEnd(end: Int) = + if (exists) fromOffsets(this.start, end, pointDelta) + else this /** A copy of this position with a different point */ - def withPoint(point: Int) = fromOffsets(this.start, this.end, point - this.start) + def withPoint(point: Int) = + if (exists) fromOffsets(this.start, this.end, point - this.start) + else this /** A synthetic copy of this position */ def toSynthetic = if (isSynthetic) this else Position(start, end)