Content-Length: 1075150 | pFad | http://github.com/scala/scala/commit/66c8adcf0f8b8d5ef3b8bbf58800bc965bcf3168

EA Unreachable components in phase assembly · scala/scala@66c8adc · GitHub
Skip to content

Commit 66c8adc

Browse files
committed
Unreachable components in phase assembly
1 parent c4e6c6e commit 66c8adc

26 files changed

+601
-77
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 && 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: 112 additions & 52 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._
@@ -33,7 +32,7 @@ trait PhaseAssembly {
3332
phasesSet.add(terminal)
3433
reporter.warning(NoPosition, "Added default terminal phase")
3534
}
36-
val graph = DependencyGraph(phasesSet)
35+
val graph = DependencyGraph(phasesSet, warnAll = !settings.isScaladoc || settings.isDebug)
3736
for (n <- settings.genPhaseGraph.valueSetByUser; d <- settings.outputDirs.getSingleOutput if !d.isVirtual)
3837
DependencyGraph.graphToDotFile(graph, Path(d.file) / File(s"$n.dot"))
3938
graph.compilerPhaseList().tap(_ => graph.warnings.foreach(msg => reporter.warning(NoPosition, msg)))
@@ -44,10 +43,14 @@ trait PhaseAssembly {
4443
*
4544
* Each vertex is labeled with its phase name.
4645
*/
47-
class DependencyGraph(order: Int, start: String, val components: Map[String, SubComponent]) {
48-
import DependencyGraph.{FollowsNow, Weight}
46+
class DependencyGraph(start: String, val components: Map[String, SubComponent]) {
47+
import DependencyGraph.{FollowsNow, Weight, weightedArrow}
4948

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

5255
private var messages: List[String] = Nil
5356
def warning(message: String): Unit = messages ::= message
@@ -60,32 +63,29 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
6063
private case class Edge(from: Int, to: Int, weight: Weight)
6164

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

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

87-
// input must be acyclic and only one FollowsNow edge is allowed between a pair of vertices
88-
private def validate(): Unit = {
84+
/** Find unreachable vertices.
85+
*
86+
* Input must be acyclic and a vertex can have only one outgoing FollowsNow edge.
87+
*/
88+
private def validated(): List[String] = {
8989
def checkFollowsNow(v: Int): Unit =
9090
adjacency(v).foldLeft(-1) { (w, e) =>
9191
if (e.weight != FollowsNow) w
@@ -122,9 +122,14 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
122122
}
123123
}
124124
walk()
125+
val unseen = ListBuffer.empty[String]
126+
for (i <- 0.until(order) if !seen(i))
127+
unseen.addOne(names(i))
128+
unseen.toList
125129
}
126130

127131
def compilerPhaseList(): List[SubComponent] = if (order == 1) List(components(start)) else {
132+
128133
// distance from source to each vertex
129134
val distance = Array.fill[Int](order)(Int.MinValue)
130135

@@ -141,11 +146,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
141146

142147
def dequeue(): Int = queue.dequeue().tap(v => enqueued(v) = false)
143148

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

146153
/** Remove a vertex from the queue and check outgoing edges:
147-
* if an edge improves (increases) the distance at the terminal,
148-
* record that as the new incoming edge and enqueue that vertex
154+
* if an edge improves (increases) the distance at the terminus of the edge,
155+
* record that as the new incoming edge to the terminus and enqueue that vertex
149156
* to propagate updates.
150157
*/
151158
def relax(): Unit = {
@@ -155,7 +162,7 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
155162
}
156163
while (!queue.isEmpty) {
157164
val v = dequeue()
158-
//if (debugging) println(s"deq ${names(v)}")
165+
if (debugging) println(s"deq ${names(v)}")
159166
for (e <- adjacency(v)) {
160167
val w = e.to
161168
/* cannot happen as `runsRightAfter: Option[String]` is the only way to introduce a `FollowsNow`
@@ -167,15 +174,16 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
167174
distance(w) = distance(v) + e.weight
168175
edgeTo(w) = e
169176
enqueue(w)
170-
//if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}")
177+
if (debugging) println(s"update ${namedEdge(e)} dist = ${distance(w)}, enq ${names(w)}")
171178
}
172179
}
173180
}
174-
//if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
181+
if (debugging) edgeTo.foreach(e => println(namedEdge(e)))
175182
}
183+
176184
/** Put the vertices in a linear order.
177185
*
178-
* `Follows` edges increase the level, `FollowsNow` don't.
186+
* `Follows` edges increase the "level", `FollowsNow` don't.
179187
* Partition by "level" or distance from start.
180188
* Partition the level into "anchors" that follow a node in the previous level, and "followers" (nodes
181189
* with a `FollowsNow` edge).
@@ -189,13 +197,14 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
189197
/*c.internal && !d.internal ||*/ c.phaseName.compareTo(d.phaseName) < 0
190198
def sortVertex(i: Int, j: Int): Boolean = sortComponents(componentOf(i), componentOf(j))
191199

192-
distance.zipWithIndex.groupBy(_._1).toList.sortBy(_._1)
193-
.flatMap { case (_, dis) =>
194-
val vs = dis.map { case (_, i) => i }
200+
val res = ListBuffer.empty[SubComponent]
201+
val levels = distance.zipWithIndex.groupBy(_._1) // distance -> (distance, node)
202+
for (level <- levels.keysIterator.filter(_ >= 0).toArray.sorted) {
203+
val vs = levels(level).map(_._2)
195204
val (anchors, followers) = vs.partition(v => edgeTo(v) == null || edgeTo(v).weight != FollowsNow)
196-
//if (debugging) println(s"d=$d, anchors=${anchors.toList.map(n => names(n))}, followers=${followers.toList.map(n => names(n))}")
205+
if (debugging) println(s"d=$level, anchors={${anchors.map(names(_)).mkString(",")}}, followers={${followers.map(names(_)).mkString(",")}}")
197206
if (followers.isEmpty)
198-
anchors.toList.map(componentOf).sortWith(sortComponents)
207+
anchors.toArray.map(componentOf).sortWith(sortComponents).foreach(res.addOne(_))
199208
else {
200209
val froms = followers.map(v => edgeTo(v).from).toSet
201210
val ends = followers.iterator.filterNot(froms).toList
@@ -206,11 +215,13 @@ class DependencyGraph(order: Int, start: String, val components: Map[String, Sub
206215
case _ => chains.find(_.apply(0) == v).foreach(deque => path.foreach(deque.append))
207216
}
208217
ends.foreach(drill(_, Nil))
209-
chains.sortWith((p, q) => sortVertex(p(0), q(0))).toList.flatten.map(componentOf)
218+
for (chain <- chains.sortWith((p, q) => sortVertex(p(0), q(0))); v <- chain)
219+
res.addOne(componentOf(v))
210220
}
211221
}
222+
res.toList
212223
}
213-
validate()
224+
for (p <- validated()) warning(s"Phase $p is not reachable from $start")
214225
relax()
215226
traverse()
216227
}
@@ -220,42 +231,91 @@ object DependencyGraph {
220231
type Weight = Int
221232
final val FollowsNow = 0
222233
final val Follows = 1
234+
def weightedArrow(w: Weight) = w match {
235+
case FollowsNow => "=>"
236+
case Follows => "->"
237+
case _ => "?>"
238+
}
223239

224240
final val Parser = "parser"
225241
final val Terminal = "terminal"
226242

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

src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ class ScaladocGlobal(settings: doc.Settings, reporter: Reporter) extends Global(
5454
phasesSet += analyzer.namerFactory
5555
phasesSet += analyzer.packageObjects
5656
phasesSet += analyzer.typerFactory
57-
phasesSet += patmatSentinel
58-
phasesSet += erasureSentinel
5957
phasesSet += terminal
6058
}
6159

@@ -65,27 +63,6 @@ class ScaladocGlobal(settings: doc.Settings, reporter: Reporter) extends Global(
6563
override def platformPhases = Nil // used by computePlatformPhases
6664
}
6765

68-
// Placeholders for plugins who wish to declare runsBefore patmat or erasure.
69-
// A bit deceptive for plugins that run after them, as scaladoc ought to -Ystop-before:patmat
70-
lazy val patmatSentinel: SubComponent = new { val global = self } with SubComponent {
71-
val phaseName = "patmat"
72-
val runsAfter = "typer" :: Nil
73-
val runsRightAfter = None
74-
def newPhase(prev: Phase): Phase = new Phase(prev) {
75-
val name = phaseName
76-
def run() = ()
77-
}
78-
}
79-
lazy val erasureSentinel: SubComponent = new { val global = self } with SubComponent {
80-
val phaseName = "erasure"
81-
val runsAfter = "patmat" :: Nil
82-
val runsRightAfter = None
83-
def newPhase(prev: Phase): Phase = new Phase(prev) {
84-
val name = phaseName
85-
def run() = ()
86-
}
87-
}
88-
8966
override def createJavadoc = if (settings.docNoJavaComments.value) false else true
9067

9168
override lazy val analyzer =
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
fatal error: Phases form a cycle: parser -> beforeparser -> parser
1+
warning: Phase beforeparser is not reachable from parser
2+
error: No warnings can be incurred under -Werror.
3+
1 warning
4+
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
warning: No phase `refchicks` for ploogin.runsAfter - did you mean refchecks?
2+
warning: No phase `java` for ploogin.runsBefore - did you mean jvm?
3+
warning: Component ploogin dropped due to bad constraint
4+
warning: Phase ploogin is not reachable from parser
5+
error: No warnings can be incurred under -Werror.
6+
phase name id description
7+
---------- -- -----------
8+
parser 1 parse source into ASTs, perform simple desugaring
9+
namer 2 resolve names, attach symbols to named trees
10+
packageobjects 3 load package objects
11+
typer 4 the meat and potatoes: type the trees
12+
superaccessors 5 add super accessors in traits and nested classes
13+
extmethods 6 add extension methods for inline classes
14+
pickler 7 serialize symbol tables
15+
refchecks 8 reference/override checking, translate nested objects
16+
patmat 9 translate match expressions
17+
uncurry 10 uncurry, translate function values to anonymous classes
18+
fields 11 synthesize accessors and fields, add bitmaps for lazy vals
19+
tailcalls 12 replace tail calls by jumps
20+
specialize 13 @specialized-driven class and method specialization
21+
explicitouter 14 this refs to outer pointers
22+
erasure 15 erase types, add interfaces for traits
23+
posterasure 16 clean up erased inline classes
24+
lambdalift 17 move nested functions to top level
25+
constructors 18 move field definitions into constructors
26+
flatten 19 eliminate inner classes
27+
mixin 20 mixin composition
28+
cleanup 21 platform-specific cleanups, generate reflective calls
29+
delambdafy 22 remove lambdas
30+
jvm 23 generate JVM bytecode
31+
terminal 24 the last phase during a compilation run

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/66c8adcf0f8b8d5ef3b8bbf58800bc965bcf3168

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy