Content-Length: 1083880 | pFad | http://github.com/scala/scala/commit/84d4940e140300c327dcb298d393ab17d77b0c4e

ED Unreachable components in phase assembly · scala/scala@84d4940 · GitHub
Skip to content

Commit 84d4940

Browse files
committed
Unreachable components in phase assembly
1 parent 1390fc3 commit 84d4940

File tree

11 files changed

+129
-76
lines changed

11 files changed

+129
-76
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import scala.tools.nsc.transform.async.AsyncPhase
4141
import scala.tools.nsc.transform.patmat.PatternMatching
4242
import scala.tools.nsc.typechecker._
4343
import scala.tools.nsc.util.ClassPath
44+
import scala.util.chaining._
4445

4546
class Global(var currentSettings: Settings, reporter0: Reporter)
4647
extends SymbolTable
@@ -749,6 +750,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
749750
computePlatformPhases() // backend/Platform.scala
750751
computePluginPhases() // plugins/Plugins.scala
751752
cullPhases(computePhaseAssembly()) // PhaseAssembly.scala
753+
.tap { _ => if (settings.fatalWarnings.value && reporter.hasWarnings) runReporting.summarizeErrors() }
752754
}
753755

754756
/* The phase descriptor list. Components that are phase factories. */

src/compiler/scala/tools/nsc/PhaseAssembly.scala

Lines changed: 107 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
package scala.tools.nsc
1414

15-
import java.util.concurrent.atomic.AtomicInteger
1615
import scala.collection.mutable, mutable.ArrayDeque, mutable.ListBuffer
1716
import scala.reflect.io.{File, Path}
1817
import scala.util.chaining._
@@ -45,10 +44,14 @@ trait PhaseAssembly {
4544
*
4645
* Each vertex is labeled with its phase name.
4746
*/
48-
class DependencyGraph(order: Int, start: String, val components: Map[String, SubComponent]) {
49-
import DependencyGraph.{FollowsNow, Weight}
47+
class DependencyGraph(start: String, val components: Map[String, SubComponent]) {
48+
import DependencyGraph.{FollowsNow, Weight, weightedArrow}
5049

51-
//private final val debugging = false
50+
require(components.contains(start), s"Start $start is not a component in ${components.keys.mkString(", ")}")
51+
52+
private final val debugging = false
53+
54+
private val order = components.size
5255

5356
private var messages: List[String] = Nil
5457
def warning(message: String): Unit = messages ::= message
@@ -61,34 +64,29 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
6164
private case class Edge(from: Int, to: Int, weight: Weight)
6265

6366
// phase names and their vertex index
64-
private val nodeCount = new AtomicInteger
65-
private val nodes = mutable.HashMap.empty[String, Int] // name to index
66-
private val names = Array.ofDim[String](order) // index to name
67+
private val names = components.keys.toArray
68+
private val nodes = names.zipWithIndex.toMap
6769

6870
/** Add the edge between named phases, where `to` follows `from`.
6971
*/
7072
private def addEdge(from: String, to: String, weight: Weight): Unit = {
71-
def getNode(name: String): Int = {
72-
def installName(name: String, n: Int): Unit =
73-
if (n >= names.length) throw new FatalError(names.mkString(s"Extra name $name; names [",",","]"))
74-
else names(n) = name
75-
nodes.getOrElseUpdate(name, nodeCount.getAndIncrement().tap(installName(name, _)))
76-
}
77-
val v = getNode(from)
78-
val w = getNode(to)
73+
val v = nodes(from)
74+
val w = nodes(to)
75+
if (debugging) println(s"add edge $from ${weightedArrow(weight)} $to")
7976
adjacency(v).find(_.to == w) match {
8077
case None =>
8178
adjacency(v) ::= Edge(from = v, to = w, weight)
8279
case Some(_) if weight == FollowsNow => // retain runsRightAfter if there is a competing constraint
8380
adjacency(v) = Edge(from = v, to = w, weight) :: adjacency(v).filterNot(_.to == w)
84-
case _ =>
81+
case _ => // ignore duplicate
8582
}
8683
}
8784

8885
/** Find unreachable vertices.
86+
*
8987
* Input must be acyclic and a vertex can have only one outgoing FollowsNow edge.
9088
*/
91-
private def validate(warn: Boolean): Set[String] = if (order == 1) Set.empty else {
89+
private def validated(): List[String] = {
9290
def checkFollowsNow(v: Int): Unit =
9391
adjacency(v).foldLeft(-1) { (w, e) =>
9492
if (e.weight != FollowsNow) w
@@ -125,13 +123,14 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
125123
}
126124
}
127125
walk()
128-
names.iterator.zipWithIndex.collect { case (n, i) if !seen(i) =>
129-
if (warn) warning(s"Dropping phase ${names(i)}, it is not reachable from $start")
130-
n
131-
}.toSet
126+
val unseen = ListBuffer.empty[String]
127+
for (i <- 0.until(order) if !seen(i))
128+
unseen.addOne(names(i))
129+
unseen.toList
132130
}
133131

134132
def compilerPhaseList(): List[SubComponent] = if (order == 1) List(components(start)) else {
133+
135134
// distance from source to each vertex
136135
val distance = Array.fill[Int](order)(Int.MinValue)
137136

@@ -148,11 +147,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
148147

149148
def dequeue(): Int = queue.dequeue().tap(v => enqueued(v) = false)
150149

151-
//def namedEdge(e: Edge): String = if (e == null) "[no edge]" else s"${names(e.from)} ${if (e.weight == FollowsNow) "=" else "-"}> ${names(e.to)}"
150+
def namedEdge(e: Edge): String =
151+
if (e == null) "[no edge]"
152+
else s"${names(e.from)}<${distance(e.from)}> ${weightedArrow(e.weight)} ${names(e.to)}<${distance(e.to)}>"
152153

153154
/** Remove a vertex from the queue and check outgoing edges:
154-
* if an edge improves (increases) the distance at the terminal,
155-
* record that as the new incoming edge and enqueue that vertex
155+
* if an edge improves (increases) the distance at the terminus of the edge,
156+
* record that as the new incoming edge to the terminus and enqueue that vertex
156157
* to propagate updates.
157158
*/
158159
def relax(): Unit = {
@@ -162,7 +163,7 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
162163
}
163164
while (!queue.isEmpty) {
164165
val v = dequeue()
165-
//if (debugging) println(s"deq ${names(v)}")
166+
if (debugging) println(s"deq ${names(v)}")
166167
for (e <- adjacency(v)) {
167168
val w = e.to
168169
/* cannot happen as `runsRightAfter: Option[String]` is the only way to introduce a `FollowsNow`
@@ -174,15 +175,16 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
174175
distance(w) = distance(v) + e.weight
175176
edgeTo(w) = e
176177
enqueue(w)
177-
//if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}")
178+
if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}")
178179
}
179180
}
180181
}
181-
//if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
182+
if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
182183
}
184+
183185
/** Put the vertices in a linear order.
184186
*
185-
* `Follows` edges increase the level, `FollowsNow` don't.
187+
* `Follows` edges increase the "level", `FollowsNow` don't.
186188
* Partition by "level" or distance from start.
187189
* Partition the level into "anchors" that follow a node in the previous level, and "followers" (nodes
188190
* with a `FollowsNow` edge).
@@ -196,13 +198,14 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
196198
/*c.internal && !d.internal ||*/ c.phaseName.compareTo(d.phaseName) < 0
197199
def sortVertex(i: Int, j: Int): Boolean = sortComponents(componentOf(i), componentOf(j))
198200

199-
distance.zipWithIndex.groupBy(_._1).toList.sortBy(_._1)
200-
.flatMap { case (_, dis) =>
201-
val vs = dis.map { case (_, i) => i }
201+
val res = ListBuffer.empty[SubComponent]
202+
val levels = distance.zipWithIndex.groupBy(_._1) // distance -> (distance, node)
203+
for (level <- levels.keysIterator.filter(_ >= 0).toArray.sorted) {
204+
val vs = levels(level).map(_._2)
202205
val (anchors, followers) = vs.partition(v => edgeTo(v) == null || edgeTo(v).weight != FollowsNow)
203-
//if (debugging) println(s"d=$d, anchors=${anchors.toList.map(n => names(n))}, followers=${followers.toList.map(n => names(n))}")
206+
if (debugging) println(s"d=$level, anchors={${anchors.map(names(_)).mkString(",")}}, followers={${followers.map(names(_)).mkString(",")}}")
204207
if (followers.isEmpty)
205-
anchors.toList.map(componentOf).sortWith(sortComponents)
208+
anchors.toArray.map(componentOf).sortWith(sortComponents).foreach(res.addOne(_))
206209
else {
207210
val froms = followers.map(v => edgeTo(v).from).toSet
208211
val ends = followers.iterator.filterNot(froms).toList
@@ -213,10 +216,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
213216
case _ => chains.find(_.apply(0) == v).foreach(deque => path.foreach(deque.append))
214217
}
215218
ends.foreach(drill(_, Nil))
216-
chains.sortWith((p, q) => sortVertex(p(0), q(0))).toList.flatten.map(componentOf)
219+
for (chain <- chains.sortWith((p, q) => sortVertex(p(0), q(0))); v <- chain)
220+
res.addOne(componentOf(v))
217221
}
218222
}
223+
res.toList
219224
}
225+
for (p <- validated()) warning(s"Dropping phase $p, which is not reachable from $start")
220226
relax()
221227
traverse()
222228
}
@@ -226,50 +232,92 @@ object DependencyGraph {
226232
type Weight = Int
227233
final val FollowsNow = 0
228234
final val Follows = 1
235+
def weightedArrow(w: Weight) = w match {
236+
case FollowsNow => "=>"
237+
case Follows => "->"
238+
case _ => "?>"
239+
}
229240

230241
final val Parser = "parser"
231242
final val Terminal = "terminal"
232243

244+
private val compilerPhases = List(
245+
"parser", "namer", "packageobjects", "typer", "superaccessors", "extmethods", "pickler",
246+
"refchecks", "patmat", "uncurry", "fields", "tailcalls", "specialize", "explicitouter",
247+
"erasure", "posterasure", "lambdalift", "constructors", "flatten", "mixin", "cleanup",
248+
"delambdafy", "jvm", "terminal",
249+
)
250+
233251
/** Create a DependencyGraph from the given phases. The graph must be acyclic.
234252
*
235253
* A component must be declared as "initial".
236254
* If no phase is "initial" but a phase is named "parser", it is taken as initial.
237255
* If no phase is "terminal" but a phase is named "terminal", it is taken as terminal.
238-
* Warnings are issued for invalid constraints (runsAfter / runsRightAfter / runsBefore) if `warn` is true.
239-
* Components without a valid "runsAfter" or "runsRightAfter" are dropped with an "unreachable" warning.
256+
*
257+
* If a component declares that it follows (runsAfter or runsRightAfter)
258+
* a phase that is not named in this run, then the component is omitted.
259+
*
260+
* If a component declares that it precedes (runsBefore)
261+
* a phase that is not named in this run, then the constraint is dropped.
262+
*
263+
* Therefore, the graph contains only edges between the input components.
264+
*
265+
* On construction (compilerPhaseList), unreachable components will incur warnings.
266+
*
267+
* Apparent typos of phase names incur warnings.
240268
*/
241-
def apply(phases: Iterable[SubComponent], warn: Boolean = true): DependencyGraph = {
269+
def apply(components: Iterable[SubComponent], warnAll: Boolean = true): DependencyGraph = {
270+
val warnings = ListBuffer.empty[String]
271+
val phases = components.iterator.filter { p =>
272+
p.phaseName.nonEmpty.tap(b => if (!b) warnings.addOne(s"Dropped component with empty name (class ${p.getClass})"))
273+
}.toArray
274+
val phaseNames = phases.iterator.map(_.phaseName).toSet
275+
def isPhaseName(s: String): Boolean = phaseNames.contains(s)
242276
val start = phases.find(_.initial)
243277
.orElse(phases.find(_.phaseName == Parser))
244278
.getOrElse(throw new AssertionError("Missing initial component"))
245279
val end = phases.find(_.terminal)
246280
.orElse(phases.find(_.phaseName == Terminal))
247281
.getOrElse(throw new AssertionError("Missing terminal component"))
248-
val graph = new DependencyGraph(phases.size, start.phaseName, phases.map(p => p.phaseName -> p).toMap)
249-
def phaseTypo(name: String) =
250-
if (graph.components.contains(name)) ""
251-
else graph.components.keysIterator.filter(util.EditDistance.levenshtein(name, _) < 3).toList match {
252-
case Nil => ""
253-
case close => s" - did you mean ${util.StringUtil.oxford(close, "or")}?"
282+
var dropped = Set.empty[String]
283+
val constraints = ListBuffer.empty[(String, String, Weight)]
284+
def addConstraint(from: String, to: String, weight: Weight): Unit = constraints.addOne((from, to, weight))
285+
def checkConstraint(p: SubComponent)(name: String, constraint: String, warn: Boolean): Boolean = isPhaseName(name) || {
286+
if (warn) {
287+
val knownNames = phaseNames ++ compilerPhases
288+
val help = if (knownNames.contains(name)) "" else
289+
knownNames.filter(util.EditDistance.levenshtein(name, _) < 3).toList match {
290+
case Nil => ""
291+
case close => s" - did you mean ${util.StringUtil.oxford(close, "or")}?"
292+
}
293+
warnings.addOne(s"No phase `$name` for ${p.phaseName}.$constraint$help")
254294
}
255-
for (p <- phases) {
256-
require(p.phaseName.nonEmpty, "Phase name must be non-empty.")
257-
def checkConstraint(name: String, constraint: String): Boolean =
258-
graph.components.contains(name).tap(ok => if (!ok && warn) graph.warning(s"No phase `$name` for ${p.phaseName}.$constraint${phaseTypo(name)}"))
259-
for (after <- p.runsRightAfter if after.nonEmpty && checkConstraint(after, "runsRightAfter"))
260-
graph.addEdge(after, p.phaseName, FollowsNow)
261-
for (after <- p.runsAfter if after.nonEmpty && !p.runsRightAfter.contains(after) && checkConstraint(after, "runsAfter"))
262-
graph.addEdge(after, p.phaseName, Follows)
263-
for (before <- p.runsBefore if before.nonEmpty && checkConstraint(before, "runsBefore"))
264-
graph.addEdge(p.phaseName, before, Follows)
265-
// Add "runsBefore terminal" to phases without (or with invalid) runsBefore
295+
false
296+
}
297+
for ((p, i) <- phases.zipWithIndex) {
298+
for (after <- p.runsRightAfter if after.nonEmpty)
299+
if (checkConstraint(p)(after, "runsRightAfter", warn=warnAll)) addConstraint(after, p.phaseName, FollowsNow)
300+
else dropped += p.phaseName
301+
for (after <- p.runsAfter if after.nonEmpty && !p.runsRightAfter.contains(after))
302+
if (checkConstraint(p)(after, "runsAfter", warn=warnAll)) addConstraint(after, p.phaseName, Follows)
303+
else dropped += p.phaseName
304+
for (before <- p.runsBefore if before.nonEmpty)
305+
if (checkConstraint(p)(before, "runsBefore", warn=warnAll)) addConstraint(p.phaseName, before, Follows)
306+
// terminal follows every phase; parser is not before every phase because traverse uses edgeTo to find "anchors"
266307
if (p != end || p == end && p == start)
267-
if (!p.runsBefore.exists(graph.components.contains))
268-
graph.addEdge(p.phaseName, end.phaseName, Follows)
308+
addConstraint(p.phaseName, end.phaseName, Follows)
309+
}
310+
if (warnAll)
311+
dropped.foreach(p => warnings.addOne(s"Component ${p} dropped due to bad constraint"))
312+
val purgedConstraints = constraints.filterInPlace {
313+
case (from, to, w) => !dropped.contains(from) && !dropped.contains(to)
314+
}.toList
315+
new DependencyGraph(start.phaseName, phases.iterator.map(p => p.phaseName -> p).toMap).tap { graph =>
316+
for ((from, to, weight) <- purgedConstraints)
317+
graph.addEdge(from, to, weight)
318+
for (warning <- warnings)
319+
graph.warning(warning)
269320
}
270-
val unreachable = graph.validate(warn)
271-
if (unreachable.isEmpty) graph
272-
else apply(phases.filterNot(p => unreachable(p.phaseName)), warn).tap(res => graph.warnings.foreach(res.warning))
273321
}
274322

275323
/** Emit a graphviz dot file for the graph.
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
warning: Dropping phase beforeparser, it is not reachable from parser
2-
sample_2.scala:8: error: type mismatch;
3-
found : String("")
4-
required: Int
5-
def f: Int = ""
6-
^
1+
warning: Dropping phase beforeparser, which is not reachable from parser
2+
error: No warnings can be incurred under -Werror.
3+
1 warning
74
1 error

test/files/neg/t7494-before-parser/sample_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//> using options -Xplugin:. -Xplugin-require:beforeparser
1+
//> using options -Xplugin:. -Xplugin-require:beforeparser -Werror
22
package sample
33

44
// just a sample that is compiled with the sample plugin enabled

test/files/neg/t8755.check

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
warning: No phase `refchicks` for ploogin.runsAfter - did you mean refchecks?
22
warning: No phase `java` for ploogin.runsBefore - did you mean jvm?
3-
warning: Dropping phase ploogin, it is not reachable from parser
3+
warning: Component ploogin dropped due to bad constraint
4+
warning: Dropping phase ploogin, which is not reachable from parser
5+
error: No warnings can be incurred under -Werror.
46
phase name id description
57
---------- -- -----------
68
parser 1 parse source into ASTs, perform simple desugaring

test/files/neg/t8755b.check

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
warning: Dropping phase ploogin, it is not reachable from parser
1+
warning: Dropping phase ploogin, which is not reachable from parser
2+
error: No warnings can be incurred under -Werror.
23
phase name id description
34
---------- -- -----------
45
parser 1 parse source into ASTs, perform simple desugaring

test/junit/scala/tools/nsc/PhaseAssemblyTest.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ class PhaseAssemblyTest {
8282
val components = names.foldLeft(parserAndTerminal(global)) { (comps, nm) =>
8383
component(global, nm, rra(nm), ra(comps, nm), runsBefore = beforeTerminal) :: comps
8484
}
85-
assertThrows[FatalError](DependencyGraph(components), _ == "Phases kerfuffle and konflikt both immediately follow phooey")
85+
val graph = DependencyGraph(components)
86+
assertThrows[FatalError](graph.compilerPhaseList(), _ == "Phases kerfuffle and konflikt both immediately follow phooey")
8687
}
8788
@Test def `trivial cycle`: Unit = {
8889
val settings = new Settings
@@ -99,7 +100,8 @@ class PhaseAssemblyTest {
99100
val components = names.foldLeft(parserAndTerminal(global)) { (comps, nm) =>
100101
component(global, nm, rra(nm), ra(comps, nm), runsBefore = beforeTerminal) :: comps
101102
}
102-
assertThrows[FatalError](DependencyGraph(components), _ == "Phases form a cycle: phooey -> kerfuffle -> konflikt -> phooey")
103+
val graph = DependencyGraph(components)
104+
assertThrows[FatalError](graph.compilerPhaseList(), _ == "Phases form a cycle: phooey -> kerfuffle -> konflikt -> phooey")
103105
}
104106
@Test def `run before tightly bound phases`: Unit = {
105107
val settings = new Settings

test/scaladoc/run/t8755.check

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
warning: No phase `refchecks` for ploogin.runsAfter
2-
warning: Dropping phase ploogin, it is not reachable from parser
2+
warning: Component ploogin dropped due to bad constraint
3+
warning: Dropping phase ploogin, which is not reachable from parser
34
parser -> namer -> packageobjects -> typer -> terminal
45
[running phase parser on newSource]
56
[running phase namer on newSource]

test/scaladoc/run/t8755b.check

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
warning: No phase `refchecks` for ploogin.runsAfter
22
warning: No phase `jvm` for ploogin.runsBefore
3-
parser -> namer -> packageobjects -> typer -> ploogin -> terminal
3+
warning: Component ploogin dropped due to bad constraint
4+
warning: Dropping phase ploogin, which is not reachable from parser
5+
parser -> namer -> packageobjects -> typer -> terminal
46
[running phase parser on newSource]
57
[running phase namer on newSource]
68
[running phase packageobjects on newSource]
79
[running phase typer on newSource]
8-
[running phase ploogin on newSource]
9-
running plugin
1010
Done.

test/scaladoc/run/t8755b/ploogin_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Ploogin(val global: Global) extends Plugin {
2323
def newPhase(prev: Phase) = new TestPhase(prev)
2424
class TestPhase(prev: Phase) extends StdPhase(prev) {
2525
override def description = TestComponent.this.description
26-
def apply(unit: CompilationUnit): Unit = println("running plugin")
26+
def apply(unit: CompilationUnit): Unit = ???
2727
}
2828
}
2929
}

test/scaladoc/run/t8755c.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
warning: No phase `refchecks` for ploogin.runsBefore
2-
warning: Dropping phase ploogin, it is not reachable from parser
2+
warning: Dropping phase ploogin, which is not reachable from parser
33
parser -> namer -> packageobjects -> typer -> terminal
44
[running phase parser on newSource]
55
[running phase namer on newSource]

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/scala/scala/commit/84d4940e140300c327dcb298d393ab17d77b0c4e

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy