Skip to content

Commit 58d1b10

Browse files
authored
Merge pull request #13173 from adampauls/wip_separate_parser_phase
Separate parsing into its own Phase
2 parents ac5e534 + bd08f7e commit 58d1b10

File tree

16 files changed

+149
-89
lines changed

16 files changed

+149
-89
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package dotc
33

44
import core._
55
import Contexts._
6-
import typer.{FrontEnd, RefChecks}
6+
import typer.{TyperPhase, RefChecks}
7+
import parsing.Parser
78
import Phases.Phase
89
import transform._
910
import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode}
@@ -36,7 +37,8 @@ class Compiler {
3637

3738
/** Phases dealing with the frontend up to trees ready for TASTY pickling */
3839
protected def frontendPhases: List[List[Phase]] =
39-
List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer
40+
List(new Parser) :: // Compiler frontend: scanner, parser
41+
List(new TyperPhase) :: // Compiler frontend: namer, typer
4042
List(new YCheckPositions) :: // YCheck positions
4143
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4244
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files

compiler/src/dotty/tools/dotc/Run.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
268268
val unit = ctx.compilationUnit
269269
val prevPhase = ctx.phase.prev // can be a mini-phase
270270
val fusedPhase = ctx.base.fusedContaining(prevPhase)
271-
val treeString = unit.tpdTree.show(using ctx.withProperty(XprintMode, Some(())))
271+
val tree =
272+
if (ctx.isAfterTyper) unit.tpdTree
273+
else unit.untpdTree
274+
val treeString = tree.show(using ctx.withProperty(XprintMode, Some(())))
272275

273276
report.echo(s"result of $unit after $fusedPhase:")
274277

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags
77
import Symbols._, StdNames._, Trees._, Phases._, ContextOps._
88
import Decorators._, transform.SymUtils._
99
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName}
10-
import typer.{FrontEnd, Namer, Checking}
10+
import typer.{TyperPhase, Namer, Checking}
1111
import util.{Property, SourceFile, SourcePosition}
1212
import config.Feature.{sourceVersion, migrateTo3, enabled}
1313
import config.SourceVersion._
@@ -1412,7 +1412,7 @@ object desugar {
14121412
def makeAnnotated(fullName: String, tree: Tree)(using Context): Annotated = {
14131413
val parts = fullName.split('.')
14141414
val ttree = typerPhase match {
1415-
case phase: FrontEnd if phase.stillToBeEntered(parts.last) =>
1415+
case phase: TyperPhase if phase.stillToBeEntered(parts.last) =>
14161416
val prefix =
14171417
parts.init.foldLeft(Ident(nme.ROOTPKG): Tree)((qual, name) =>
14181418
Select(qual, name.toTermName))

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,9 @@ object Contexts {
388388
// to be used as a default value.
389389
compilationUnit != null && compilationUnit.isJava
390390

391-
/** Is current phase after FrontEnd? */
391+
/** Is current phase after TyperPhase? */
392392
final def isAfterTyper = base.isAfterTyper(phase)
393+
final def isTyper = base.isTyper(phase)
393394

394395
/** Is this a context for the members of a class definition? */
395396
def isClassDefContext: Boolean =

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

+19-6
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import scala.collection.mutable.ListBuffer
1313
import dotty.tools.dotc.transform.MegaPhase._
1414
import dotty.tools.dotc.transform._
1515
import Periods._
16-
import typer.{FrontEnd, RefChecks}
16+
import parsing.{ Parser}
17+
import typer.{TyperPhase, RefChecks}
1718
import typer.ImportInfo.withRootImports
1819
import ast.tpd
1920
import scala.annotation.internal.sharable
21+
import scala.util.control.NonFatal
2022

2123
object Phases {
2224

@@ -64,7 +66,6 @@ object Phases {
6466
YCheckAfter: List[String])(using Context): List[Phase] = {
6567
val fusedPhases = ListBuffer[Phase]()
6668
var prevPhases: Set[String] = Set.empty
67-
val YCheckAll = YCheckAfter.contains("all")
6869

6970
var stop = false
7071

@@ -106,7 +107,7 @@ object Phases {
106107
phase
107108
}
108109
fusedPhases += phaseToAdd
109-
val shouldAddYCheck = YCheckAfter.containsPhase(phaseToAdd) || YCheckAll
110+
val shouldAddYCheck = filteredPhases(i).exists(_.isCheckable) && YCheckAfter.containsPhase(phaseToAdd)
110111
if (shouldAddYCheck) {
111112
val checker = new TreeChecker
112113
fusedPhases += checker
@@ -194,6 +195,7 @@ object Phases {
194195
config.println(s"nextDenotTransformerId = ${nextDenotTransformerId.toList}")
195196
}
196197

198+
private var myParserPhase: Phase = _
197199
private var myTyperPhase: Phase = _
198200
private var myPostTyperPhase: Phase = _
199201
private var mySbtExtractDependenciesPhase: Phase = _
@@ -215,6 +217,7 @@ object Phases {
215217
private var myFlattenPhase: Phase = _
216218
private var myGenBCodePhase: Phase = _
217219

220+
final def parserPhase: Phase = myParserPhase
218221
final def typerPhase: Phase = myTyperPhase
219222
final def postTyperPhase: Phase = myPostTyperPhase
220223
final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase
@@ -239,7 +242,8 @@ object Phases {
239242
private def setSpecificPhases() = {
240243
def phaseOfClass(pclass: Class[?]) = phases.find(pclass.isInstance).getOrElse(NoPhase)
241244

242-
myTyperPhase = phaseOfClass(classOf[FrontEnd])
245+
myParserPhase = phaseOfClass(classOf[Parser])
246+
myTyperPhase = phaseOfClass(classOf[TyperPhase])
243247
myPostTyperPhase = phaseOfClass(classOf[PostTyper])
244248
mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies])
245249
myPicklerPhase = phaseOfClass(classOf[Pickler])
@@ -262,6 +266,7 @@ object Phases {
262266
}
263267

264268
final def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id
269+
final def isTyper(phase: Phase): Boolean = phase.id == typerPhase.id
265270
}
266271

267272
abstract class Phase {
@@ -313,8 +318,8 @@ object Phases {
313318
*/
314319
def checkPostCondition(tree: tpd.Tree)(using Context): Unit = ()
315320

316-
/** Is this phase the standard typerphase? True for FrontEnd, but
317-
* not for other first phases (such as FromTasty). The predicate
321+
/** Is this phase the standard typerphase? True for TyperPhase, but
322+
* not for other first phases (such as FromTasty or Parser). The predicate
318323
* is tested in some places that perform checks and corrections. It's
319324
* different from ctx.isAfterTyper (and cheaper to test).
320325
*/
@@ -402,9 +407,17 @@ object Phases {
402407
final def iterator: Iterator[Phase] =
403408
Iterator.iterate(this)(_.next) takeWhile (_.hasNext)
404409

410+
final def monitor(doing: String)(body: => Unit)(using Context): Unit =
411+
try body
412+
catch
413+
case NonFatal(ex) =>
414+
report.echo(s"exception occurred while $doing ${ctx.compilationUnit}")
415+
throw ex
416+
405417
override def toString: String = phaseName
406418
}
407419

420+
def parserPhase(using Context): Phase = ctx.base.parserPhase
408421
def typerPhase(using Context): Phase = ctx.base.typerPhase
409422
def postTyperPhase(using Context): Phase = ctx.base.postTyperPhase
410423
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ object SymDenotations {
573573
case _ =>
574574
// Otherwise, no completion is necessary, see the preconditions of `markAbsent()`.
575575
(myInfo `eq` NoType)
576-
|| is(Invisible) && !ctx.isAfterTyper
576+
|| is(Invisible) && ctx.isTyper
577577
|| is(ModuleVal, butNot = Package) && moduleClass.isAbsent(canForce)
578578
}
579579

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -2257,7 +2257,7 @@ object Types {
22572257
if (!d.exists && !allowPrivate && ctx.mode.is(Mode.Interactive))
22582258
// In the IDE we might change a public symbol to private, and would still expect to find it.
22592259
d = memberDenot(prefix, name, true)
2260-
if (!d.exists && ctx.phaseId > FirstPhaseId && lastDenotation.isInstanceOf[SymDenotation])
2260+
if (!d.exists && ctx.isAfterTyper && lastDenotation.isInstanceOf[SymDenotation])
22612261
// name has changed; try load in earlier phase and make current
22622262
d = atPhase(ctx.phaseId - 1)(memberDenot(name, allowPrivate)).current
22632263
if (d.isOverloaded)

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package interactive
44

55
import core._
66
import Phases._
7+
import parsing._
78
import typer._
89

910
class InteractiveCompiler extends Compiler {
@@ -12,7 +13,8 @@ class InteractiveCompiler extends Compiler {
1213
// This could be improved by reporting errors back to the IDE
1314
// after each phase group instead of waiting for the pipeline to finish.
1415
override def phases: List[List[Phase]] = List(
15-
List(new FrontEnd),
16+
List(new Parser),
17+
List(new TyperPhase),
1618
List(new transform.SetRootTree),
1719
List(new transform.CookComments)
1820
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package dotty.tools.dotc.parsing
2+
3+
import dotty.tools.dotc.ast.Trees
4+
import dotty.tools.dotc.config.Config
5+
import dotty.tools.dotc.config.Printers.{ default, typr }
6+
import dotty.tools.dotc.core.Contexts.{ Context, ctx }
7+
import dotty.tools.dotc.core.Phases.Phase
8+
import dotty.tools.dotc.core.Symbols.defn
9+
import dotty.tools.dotc.typer.ImportInfo.withRootImports
10+
import dotty.tools.dotc.{ CompilationUnit, ast, report }
11+
import dotty.tools.dotc.util.{ NoSourcePosition, SourcePosition }
12+
import dotty.tools.dotc.util.Stats.record
13+
import dotty.tools.unsupported
14+
15+
class Parser extends Phase {
16+
17+
override def phaseName: String = Parser.name
18+
19+
// We run TreeChecker only after type checking
20+
override def isCheckable: Boolean = false
21+
22+
/** The position of the first XML literal encountered while parsing,
23+
* NoSourcePosition if there were no XML literals.
24+
*/
25+
private[dotc] var firstXmlPos: SourcePosition = NoSourcePosition
26+
27+
def parse(using Context) = monitor("parser") {
28+
val unit = ctx.compilationUnit
29+
unit.untpdTree =
30+
if (unit.isJava) new JavaParsers.JavaParser(unit.source).parse()
31+
else {
32+
val p = new Parsers.Parser(unit.source)
33+
// p.in.debugTokenStream = true
34+
val tree = p.parse()
35+
if (p.firstXmlPos.exists && !firstXmlPos.exists)
36+
firstXmlPos = p.firstXmlPos
37+
tree
38+
}
39+
40+
val printer = if (ctx.settings.Xprint.value.contains(Parser.name)) default else typr
41+
printer.println("parsed:\n" + unit.untpdTree.show)
42+
if (Config.checkPositions)
43+
unit.untpdTree.checkPos(nonOverlapping = !unit.isJava && !ctx.reporter.hasErrors)
44+
}
45+
46+
47+
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = {
48+
val unitContexts =
49+
for unit <- units yield
50+
report.inform(s"parsing ${unit.source}")
51+
ctx.fresh.setCompilationUnit(unit).withRootImports
52+
53+
unitContexts.foreach(parse(using _))
54+
record("parsedTrees", ast.Trees.ntrees)
55+
56+
unitContexts.map(_.compilationUnit)
57+
}
58+
59+
def run(using Context): Unit = unsupported("run")
60+
}
61+
62+
object Parser{
63+
val name: String = "parser"
64+
}

compiler/src/dotty/tools/dotc/transform/Pickler.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class Pickler extends Phase {
113113
val ctx2 = ctx.fresh.setSetting(ctx.settings.YreadComments, true)
114114
testUnpickler(
115115
using ctx2
116-
.setPeriod(Period(ctx.runId + 1, FirstPhaseId))
116+
.setPeriod(Period(ctx.runId + 1, ctx.base.typerPhase.id))
117117
.setReporter(new ThrowingReporter(ctx.reporter))
118118
.addMode(Mode.ReadPositions)
119119
.addMode(Mode.PrintShowExceptions))

compiler/src/dotty/tools/dotc/typer/FrontEnd.scala renamed to compiler/src/dotty/tools/dotc/typer/TyperPhase.scala

+33-43
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@ import Decorators._
1010
import ImportInfo.withRootImports
1111
import parsing.JavaParsers.JavaParser
1212
import parsing.Parsers.Parser
13+
import parsing.{Parser => ParserPhase}
1314
import config.Config
1415
import config.Printers.{typr, default}
1516
import util.Stats._
1617
import util.{ SourcePosition, NoSourcePosition }
1718
import scala.util.control.NonFatal
1819
import ast.Trees._
1920

20-
class FrontEnd extends Phase {
21+
/**
22+
*
23+
* @param addRootImports Set to false in the REPL. Calling [[ImportInfo.withRootImports]] on the [[Context]]
24+
* for each [[CompilationUnit]] causes dotty.tools.repl.ScriptedTests to fail.
25+
*/
26+
class TyperPhase(addRootImports: Boolean = true) extends Phase {
2127

22-
override def phaseName: String = FrontEnd.name
28+
override def phaseName: String = TyperPhase.name
2329
override def isTyper: Boolean = true
2430
import ast.tpd
2531

@@ -28,43 +34,14 @@ class FrontEnd extends Phase {
2834
/** The contexts for compilation units that are parsed but not yet entered */
2935
private var remaining: List[Context] = Nil
3036

31-
/** The position of the first XML literal encountered while parsing,
32-
* NoSourcePosition if there were no XML literals.
33-
*/
34-
private var firstXmlPos: SourcePosition = NoSourcePosition
35-
3637
/** Does a source file ending with `<name>.scala` belong to a compilation unit
3738
* that is parsed but not yet entered?
3839
*/
3940
def stillToBeEntered(name: String): Boolean =
4041
remaining.exists(_.compilationUnit.toString.endsWith(name + ".scala"))
4142

42-
def monitor(doing: String)(body: => Unit)(using Context): Unit =
43-
try body
44-
catch
45-
case NonFatal(ex) =>
46-
report.echo(s"exception occurred while $doing ${ctx.compilationUnit}")
47-
throw ex
48-
49-
def parse(using Context): Unit = monitor("parsing") {
50-
val unit = ctx.compilationUnit
51-
52-
unit.untpdTree =
53-
if (unit.isJava) new JavaParser(unit.source).parse()
54-
else {
55-
val p = new Parser(unit.source)
56-
// p.in.debugTokenStream = true
57-
val tree = p.parse()
58-
if (p.firstXmlPos.exists && !firstXmlPos.exists)
59-
firstXmlPos = p.firstXmlPos
60-
tree
61-
}
62-
63-
val printer = if (ctx.settings.Xprint.value.contains("parser")) default else typr
64-
printer.println("parsed:\n" + unit.untpdTree.show)
65-
if (Config.checkPositions)
66-
unit.untpdTree.checkPos(nonOverlapping = !unit.isJava && !ctx.reporter.hasErrors)
67-
}
43+
// Run regardless of parsing errors
44+
override def isRunnable(implicit ctx: Context): Boolean = true
6845

6946
def enterSyms(using Context): Unit = monitor("indexing") {
7047
val unit = ctx.compilationUnit
@@ -103,19 +80,26 @@ class FrontEnd extends Phase {
10380
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
10481
val unitContexts =
10582
for unit <- units yield
106-
report.inform(s"compiling ${unit.source}")
107-
ctx.fresh.setCompilationUnit(unit).withRootImports
108-
unitContexts.foreach(parse(using _))
109-
record("parsedTrees", ast.Trees.ntrees)
83+
val newCtx = ctx.fresh.setPhase(this.start).setCompilationUnit(unit)
84+
report.inform(s"typing ${unit.source}")
85+
if (addRootImports)
86+
newCtx.withRootImports
87+
else
88+
newCtx
89+
11090
remaining = unitContexts
11191
while remaining.nonEmpty do
11292
enterSyms(using remaining.head)
11393
remaining = remaining.tail
114-
115-
if firstXmlPos.exists && !defn.ScalaXmlPackageClass.exists then
116-
report.error("""To support XML literals, your project must depend on scala-xml.
117-
|See https://github.com/scala/scala-xml for more information.""".stripMargin,
118-
firstXmlPos)
94+
val firstXmlPos = ctx.base.parserPhase match {
95+
case p: ParserPhase =>
96+
if p.firstXmlPos.exists && !defn.ScalaXmlPackageClass.exists then
97+
report.error(
98+
"""To support XML literals, your project must depend on scala-xml.
99+
|See https://github.com/scala/scala-xml for more information.""".stripMargin,
100+
p.firstXmlPos)
101+
case _ =>
102+
}
119103

120104
unitContexts.foreach(typeCheck(using _))
121105
record("total trees after typer", ast.Trees.ntrees)
@@ -128,6 +112,12 @@ class FrontEnd extends Phase {
128112
def run(using Context): Unit = unsupported("run")
129113
}
130114

131-
object FrontEnd {
115+
object TyperPhase {
132116
val name: String = "typer"
133117
}
118+
119+
@deprecated(message = "FrontEnd has been split into TyperPhase and Parser. Refer to one or the other.")
120+
object FrontEnd {
121+
// For backwards compatibility: some plugins refer to FrontEnd so that they can schedule themselves after it.
122+
val name: String = TyperPhase.name
123+
}

0 commit comments

Comments
 (0)