Skip to content

Commit 6c20773

Browse files
authored
Merge pull request #2692 from dotty-staging/change-harden-ide-2
Harden IDE, 2nd attempt
2 parents fa9f8b2 + c7a5236 commit 6c20773

File tree

9 files changed

+71
-20
lines changed

9 files changed

+71
-20
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import collection.mutable.ListBuffer
1313
import parsing.Tokens.Token
1414
import printing.Printer
1515
import util.{Stats, Attachment, Property, DotClass}
16+
import config.Config
1617
import annotation.unchecked.uncheckedVariance
1718
import language.implicitConversions
1819

@@ -113,10 +114,30 @@ object Trees {
113114
* type. (Overridden by empty trees)
114115
*/
115116
def withType(tpe: Type)(implicit ctx: Context): ThisTree[Type] = {
116-
if (tpe.isInstanceOf[ErrorType]) assert(ctx.reporter.errorsReported)
117+
if (tpe.isInstanceOf[ErrorType])
118+
assert(ctx.reporter.errorsReported)
119+
else if (Config.checkTreesConsistent)
120+
checkChildrenTyped(productIterator)
117121
withTypeUnchecked(tpe)
118122
}
119123

124+
/** Check that typed trees don't refer to untyped ones, except if
125+
* - the parent tree is an import, or
126+
* - the child tree is an identifier, or
127+
* - errors were reported
128+
*/
129+
private def checkChildrenTyped(it: Iterator[Any])(implicit ctx: Context): Unit =
130+
if (!this.isInstanceOf[Import[_]])
131+
while (it.hasNext)
132+
it.next match {
133+
case x: Ident[_] => // untyped idents are used in a number of places in typed trees
134+
case x: Tree[_] =>
135+
assert(x.hasType || ctx.reporter.errorsReported,
136+
s"$this has untyped child $x")
137+
case xs: List[_] => checkChildrenTyped(xs.iterator)
138+
case _ =>
139+
}
140+
120141
def withTypeUnchecked(tpe: Type): ThisTree[Type] = {
121142
val tree =
122143
(if (myTpe == null ||
@@ -1177,6 +1198,8 @@ object Trees {
11771198
case Thicket(trees) =>
11781199
val trees1 = transform(trees)
11791200
if (trees1 eq trees) tree else Thicket(trees1)
1201+
case _ if ctx.reporter.errorsReported =>
1202+
tree
11801203
}
11811204

11821205
def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] =
@@ -1282,6 +1305,9 @@ object Trees {
12821305
this(this(x, arg), annot)
12831306
case Thicket(ts) =>
12841307
this(x, ts)
1308+
case _ if ctx.reporter.errorsReported =>
1309+
// in case of errors it may be that typed trees point to untyped ones.
1310+
x
12851311
}
12861312
}
12871313
}

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ object Config {
7272
/** Check positions for consistency after parsing */
7373
final val checkPositions = true
7474

75+
/** Check that typed trees don't point to untyped ones */
76+
final val checkTreesConsistent = false
77+
7578
/** Show subtype traces for all deep subtype recursions */
7679
final val traceDeepSubTypeRecursions = false
7780

@@ -163,4 +166,7 @@ object Config {
163166
* when findMemberLimit is set.
164167
*/
165168
final val PendingFindMemberLimit = LogPendingFindMemberThreshold * 4
169+
170+
/** When in IDE, turn StaleSymbol errors into warnings instead of crashing */
171+
final val ignoreStaleInIDE = true
166172
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ object Denotations {
722722
* if denotation is no longer valid.
723723
*/
724724
private def bringForward()(implicit ctx: Context): SingleDenotation = this match {
725-
case denot: SymDenotation if ctx.stillValid(denot) =>
725+
case denot: SymDenotation if ctx.stillValid(denot) || ctx.acceptStale(denot) =>
726726
assert(ctx.runId > validFor.runId || ctx.settings.YtestPickler.value, // mixing test pickler with debug printing can travel back in time
727727
s"denotation $denot invalid in run ${ctx.runId}. ValidFor: $validFor")
728728
var d: SingleDenotation = denot
@@ -918,13 +918,15 @@ object Denotations {
918918
old.nextInRun = this
919919
}
920920

921-
def staleSymbolError(implicit ctx: Context) = {
921+
def staleSymbolError(implicit ctx: Context) =
922+
throw new StaleSymbol(staleSymbolMsg)
923+
924+
def staleSymbolMsg(implicit ctx: Context): String = {
922925
def ownerMsg = this match {
923926
case denot: SymDenotation => s"in ${denot.owner}"
924927
case _ => ""
925928
}
926-
def msg = s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${myValidFor}, is referred to in run ${ctx.period}"
927-
throw new StaleSymbol(msg)
929+
s"stale symbol; $this#${symbol.id} $ownerMsg, defined in ${myValidFor}, is referred to in run ${ctx.period}"
928930
}
929931

930932
/** The period (interval of phases) for which there exists

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,7 @@ object Mode {
9090
* In this case, identifiers should never be imported.
9191
*/
9292
val InPackageClauseName = newMode(18, "InPackageClauseName")
93+
94+
/** We are in the IDE */
95+
val Interactive = newMode(20, "Interactive")
9396
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ trait SymDenotations { this: Context =>
9999
explain("denotation is not a SymDenotation")
100100
}
101101
}
102+
103+
/** Configurable: Accept stale symbol with warning if in IDE */
104+
def acceptStale(denot: SingleDenotation): Boolean =
105+
mode.is(Mode.Interactive) && Config.ignoreStaleInIDE && {
106+
ctx.echo(denot.staleSymbolMsg)
107+
true
108+
}
102109
}
103110

104111
object SymDenotations {

compiler/src/dotty/tools/dotc/interactive/Interactive.scala

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,20 @@ object Interactive {
131131
val buf = new mutable.ListBuffer[SourceTree]
132132

133133
trees foreach { case SourceTree(topTree, source) =>
134-
(new TreeTraverser {
135-
override def traverse(tree: Tree)(implicit ctx: Context) = {
134+
(new untpd.TreeTraverser {
135+
override def traverse(tree: untpd.Tree)(implicit ctx: Context) = {
136136
tree match {
137-
case _: Inlined =>
137+
case _: untpd.Inlined =>
138138
// Skip inlined trees
139-
case tree: NameTree
140-
if tree.symbol.exists
141-
&& !tree.symbol.is(Synthetic)
142-
&& tree.pos.exists
143-
&& !tree.pos.isZeroExtent
144-
&& (includeReferences || isDefinition(tree))
145-
&& treePredicate(tree) =>
146-
buf += SourceTree(tree, source)
139+
case utree: untpd.NameTree if tree.hasType =>
140+
val tree = utree.asInstanceOf[tpd.NameTree]
141+
if (tree.symbol.exists
142+
&& !tree.symbol.is(Synthetic)
143+
&& tree.pos.exists
144+
&& !tree.pos.isZeroExtent
145+
&& (includeReferences || isDefinition(tree))
146+
&& treePredicate(tree))
147+
buf += SourceTree(tree, source)
147148
case _ =>
148149
}
149150
traverseChildren(tree)

compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package interactive
55
import core._
66
import Phases._
77
import typer._
8+
import Contexts._
89

910
class InteractiveCompiler extends Compiler {
1011
// TODO: Figure out what phases should be run in IDEs

compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class InteractiveDriver(settings: List[String]) extends Driver {
3333
override def sourcesRequired = false
3434

3535
private val myInitCtx: Context = {
36-
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions)
36+
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive)
3737
rootCtx.setSetting(rootCtx.settings.YretainTrees, true)
3838
val ctx = setup(settings.toArray, rootCtx)._2
3939
ctx.initialize()(ctx)

compiler/src/dotty/tools/dotc/util/Positions.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,18 @@ object Positions {
100100

101101
/** A copy of this position with a different start */
102102
def withStart(start: Int) =
103-
fromOffsets(start, this.end, if (isSynthetic) SyntheticPointDelta else this.point - start)
103+
if (exists) fromOffsets(start, this.end, if (isSynthetic) SyntheticPointDelta else this.point - start)
104+
else this
104105

105106
/** A copy of this position with a different end */
106-
def withEnd(end: Int) = fromOffsets(this.start, end, pointDelta)
107+
def withEnd(end: Int) =
108+
if (exists) fromOffsets(this.start, end, pointDelta)
109+
else this
107110

108111
/** A copy of this position with a different point */
109-
def withPoint(point: Int) = fromOffsets(this.start, this.end, point - this.start)
112+
def withPoint(point: Int) =
113+
if (exists) fromOffsets(this.start, this.end, point - this.start)
114+
else this
110115

111116
/** A synthetic copy of this position */
112117
def toSynthetic = if (isSynthetic) this else Position(start, end)

0 commit comments

Comments
 (0)