diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 911628209971..b2daaa4701dd 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -3,7 +3,8 @@ package dotc import core._ import Contexts._ -import typer.{FrontEnd, RefChecks} +import typer.{TyperPhase, RefChecks} +import parsing.Parser import Phases.Phase import transform._ import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode} @@ -36,7 +37,8 @@ class Compiler { /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = - List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer + List(new Parser) :: // Compiler frontend: scanner, parser + List(new TyperPhase) :: // Compiler frontend: namer, typer List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 0afea7988958..d75042d5a238 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -268,7 +268,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint val unit = ctx.compilationUnit val prevPhase = ctx.phase.prev // can be a mini-phase val fusedPhase = ctx.base.fusedContaining(prevPhase) - val treeString = unit.tpdTree.show(using ctx.withProperty(XprintMode, Some(()))) + val tree = + if (ctx.isAfterTyper) unit.tpdTree + else unit.untpdTree + val treeString = tree.show(using ctx.withProperty(XprintMode, Some(()))) report.echo(s"result of $unit after $fusedPhase:") diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 870af91e083c..057a93196154 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -7,7 +7,7 @@ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags import Symbols._, StdNames._, Trees._, Phases._, ContextOps._ import Decorators._, transform.SymUtils._ import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} -import typer.{FrontEnd, Namer, Checking} +import typer.{TyperPhase, Namer, Checking} import util.{Property, SourceFile, SourcePosition} import config.Feature.{sourceVersion, migrateTo3, enabled} import config.SourceVersion._ @@ -1412,7 +1412,7 @@ object desugar { def makeAnnotated(fullName: String, tree: Tree)(using Context): Annotated = { val parts = fullName.split('.') val ttree = typerPhase match { - case phase: FrontEnd if phase.stillToBeEntered(parts.last) => + case phase: TyperPhase if phase.stillToBeEntered(parts.last) => val prefix = parts.init.foldLeft(Ident(nme.ROOTPKG): Tree)((qual, name) => Select(qual, name.toTermName)) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 42ec0ee1c857..ced6502833e3 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -388,8 +388,9 @@ object Contexts { // to be used as a default value. compilationUnit != null && compilationUnit.isJava - /** Is current phase after FrontEnd? */ + /** Is current phase after TyperPhase? */ final def isAfterTyper = base.isAfterTyper(phase) + final def isTyper = base.isTyper(phase) /** Is this a context for the members of a class definition? */ def isClassDefContext: Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 185dd569a993..b1268d7034a9 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -13,10 +13,12 @@ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.transform.MegaPhase._ import dotty.tools.dotc.transform._ import Periods._ -import typer.{FrontEnd, RefChecks} +import parsing.{ Parser} +import typer.{TyperPhase, RefChecks} import typer.ImportInfo.withRootImports import ast.tpd import scala.annotation.internal.sharable +import scala.util.control.NonFatal object Phases { @@ -64,7 +66,6 @@ object Phases { YCheckAfter: List[String])(using Context): List[Phase] = { val fusedPhases = ListBuffer[Phase]() var prevPhases: Set[String] = Set.empty - val YCheckAll = YCheckAfter.contains("all") var stop = false @@ -106,7 +107,7 @@ object Phases { phase } fusedPhases += phaseToAdd - val shouldAddYCheck = YCheckAfter.containsPhase(phaseToAdd) || YCheckAll + val shouldAddYCheck = filteredPhases(i).exists(_.isCheckable) && YCheckAfter.containsPhase(phaseToAdd) if (shouldAddYCheck) { val checker = new TreeChecker fusedPhases += checker @@ -194,6 +195,7 @@ object Phases { config.println(s"nextDenotTransformerId = ${nextDenotTransformerId.toList}") } + private var myParserPhase: Phase = _ private var myTyperPhase: Phase = _ private var myPostTyperPhase: Phase = _ private var mySbtExtractDependenciesPhase: Phase = _ @@ -215,6 +217,7 @@ object Phases { private var myFlattenPhase: Phase = _ private var myGenBCodePhase: Phase = _ + final def parserPhase: Phase = myParserPhase final def typerPhase: Phase = myTyperPhase final def postTyperPhase: Phase = myPostTyperPhase final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase @@ -239,7 +242,8 @@ object Phases { private def setSpecificPhases() = { def phaseOfClass(pclass: Class[?]) = phases.find(pclass.isInstance).getOrElse(NoPhase) - myTyperPhase = phaseOfClass(classOf[FrontEnd]) + myParserPhase = phaseOfClass(classOf[Parser]) + myTyperPhase = phaseOfClass(classOf[TyperPhase]) myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) @@ -262,6 +266,7 @@ object Phases { } final def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id + final def isTyper(phase: Phase): Boolean = phase.id == typerPhase.id } abstract class Phase { @@ -313,8 +318,8 @@ object Phases { */ def checkPostCondition(tree: tpd.Tree)(using Context): Unit = () - /** Is this phase the standard typerphase? True for FrontEnd, but - * not for other first phases (such as FromTasty). The predicate + /** Is this phase the standard typerphase? True for TyperPhase, but + * not for other first phases (such as FromTasty or Parser). The predicate * is tested in some places that perform checks and corrections. It's * different from ctx.isAfterTyper (and cheaper to test). */ @@ -402,9 +407,17 @@ object Phases { final def iterator: Iterator[Phase] = Iterator.iterate(this)(_.next) takeWhile (_.hasNext) + final def monitor(doing: String)(body: => Unit)(using Context): Unit = + try body + catch + case NonFatal(ex) => + report.echo(s"exception occurred while $doing ${ctx.compilationUnit}") + throw ex + override def toString: String = phaseName } + def parserPhase(using Context): Phase = ctx.base.parserPhase def typerPhase(using Context): Phase = ctx.base.typerPhase def postTyperPhase(using Context): Phase = ctx.base.postTyperPhase def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 3e72f224cab0..a7d45abd7a41 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -573,7 +573,7 @@ object SymDenotations { case _ => // Otherwise, no completion is necessary, see the preconditions of `markAbsent()`. (myInfo `eq` NoType) - || is(Invisible) && !ctx.isAfterTyper + || is(Invisible) && ctx.isTyper || is(ModuleVal, butNot = Package) && moduleClass.isAbsent(canForce) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0b13fe0dbb3a..0d01668be2f5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2257,7 +2257,7 @@ object Types { if (!d.exists && !allowPrivate && ctx.mode.is(Mode.Interactive)) // In the IDE we might change a public symbol to private, and would still expect to find it. d = memberDenot(prefix, name, true) - if (!d.exists && ctx.phaseId > FirstPhaseId && lastDenotation.isInstanceOf[SymDenotation]) + if (!d.exists && ctx.isAfterTyper && lastDenotation.isInstanceOf[SymDenotation]) // name has changed; try load in earlier phase and make current d = atPhase(ctx.phaseId - 1)(memberDenot(name, allowPrivate)).current if (d.isOverloaded) diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala index b3ef40dec8a3..38a93125a342 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala @@ -4,6 +4,7 @@ package interactive import core._ import Phases._ +import parsing._ import typer._ class InteractiveCompiler extends Compiler { @@ -12,7 +13,8 @@ class InteractiveCompiler extends Compiler { // This could be improved by reporting errors back to the IDE // after each phase group instead of waiting for the pipeline to finish. override def phases: List[List[Phase]] = List( - List(new FrontEnd), + List(new Parser), + List(new TyperPhase), List(new transform.SetRootTree), List(new transform.CookComments) ) diff --git a/compiler/src/dotty/tools/dotc/parsing/ParserPhase.scala b/compiler/src/dotty/tools/dotc/parsing/ParserPhase.scala new file mode 100644 index 000000000000..a5dfcd77f476 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/parsing/ParserPhase.scala @@ -0,0 +1,64 @@ +package dotty.tools.dotc.parsing + +import dotty.tools.dotc.ast.Trees +import dotty.tools.dotc.config.Config +import dotty.tools.dotc.config.Printers.{ default, typr } +import dotty.tools.dotc.core.Contexts.{ Context, ctx } +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.typer.ImportInfo.withRootImports +import dotty.tools.dotc.{ CompilationUnit, ast, report } +import dotty.tools.dotc.util.{ NoSourcePosition, SourcePosition } +import dotty.tools.dotc.util.Stats.record +import dotty.tools.unsupported + +class Parser extends Phase { + + override def phaseName: String = Parser.name + + // We run TreeChecker only after type checking + override def isCheckable: Boolean = false + + /** The position of the first XML literal encountered while parsing, + * NoSourcePosition if there were no XML literals. + */ + private[dotc] var firstXmlPos: SourcePosition = NoSourcePosition + + def parse(using Context) = monitor("parser") { + val unit = ctx.compilationUnit + unit.untpdTree = + if (unit.isJava) new JavaParsers.JavaParser(unit.source).parse() + else { + val p = new Parsers.Parser(unit.source) + // p.in.debugTokenStream = true + val tree = p.parse() + if (p.firstXmlPos.exists && !firstXmlPos.exists) + firstXmlPos = p.firstXmlPos + tree + } + + val printer = if (ctx.settings.Xprint.value.contains(Parser.name)) default else typr + printer.println("parsed:\n" + unit.untpdTree.show) + if (Config.checkPositions) + unit.untpdTree.checkPos(nonOverlapping = !unit.isJava && !ctx.reporter.hasErrors) + } + + + override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = { + val unitContexts = + for unit <- units yield + report.inform(s"parsing ${unit.source}") + ctx.fresh.setCompilationUnit(unit).withRootImports + + unitContexts.foreach(parse(using _)) + record("parsedTrees", ast.Trees.ntrees) + + unitContexts.map(_.compilationUnit) + } + + def run(using Context): Unit = unsupported("run") +} + +object Parser{ + val name: String = "parser" +} diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index d683720a2c3d..6805160dbf32 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -113,7 +113,7 @@ class Pickler extends Phase { val ctx2 = ctx.fresh.setSetting(ctx.settings.YreadComments, true) testUnpickler( using ctx2 - .setPeriod(Period(ctx.runId + 1, FirstPhaseId)) + .setPeriod(Period(ctx.runId + 1, ctx.base.typerPhase.id)) .setReporter(new ThrowingReporter(ctx.reporter)) .addMode(Mode.ReadPositions) .addMode(Mode.PrintShowExceptions)) diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala similarity index 63% rename from compiler/src/dotty/tools/dotc/typer/FrontEnd.scala rename to compiler/src/dotty/tools/dotc/typer/TyperPhase.scala index 0f51b19ddf91..01082ba7dcbf 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala @@ -10,6 +10,7 @@ import Decorators._ import ImportInfo.withRootImports import parsing.JavaParsers.JavaParser import parsing.Parsers.Parser +import parsing.{Parser => ParserPhase} import config.Config import config.Printers.{typr, default} import util.Stats._ @@ -17,9 +18,14 @@ import util.{ SourcePosition, NoSourcePosition } import scala.util.control.NonFatal import ast.Trees._ -class FrontEnd extends Phase { +/** + * + * @param addRootImports Set to false in the REPL. Calling [[ImportInfo.withRootImports]] on the [[Context]] + * for each [[CompilationUnit]] causes dotty.tools.repl.ScriptedTests to fail. + */ +class TyperPhase(addRootImports: Boolean = true) extends Phase { - override def phaseName: String = FrontEnd.name + override def phaseName: String = TyperPhase.name override def isTyper: Boolean = true import ast.tpd @@ -28,43 +34,14 @@ class FrontEnd extends Phase { /** The contexts for compilation units that are parsed but not yet entered */ private var remaining: List[Context] = Nil - /** The position of the first XML literal encountered while parsing, - * NoSourcePosition if there were no XML literals. - */ - private var firstXmlPos: SourcePosition = NoSourcePosition - /** Does a source file ending with `.scala` belong to a compilation unit * that is parsed but not yet entered? */ def stillToBeEntered(name: String): Boolean = remaining.exists(_.compilationUnit.toString.endsWith(name + ".scala")) - def monitor(doing: String)(body: => Unit)(using Context): Unit = - try body - catch - case NonFatal(ex) => - report.echo(s"exception occurred while $doing ${ctx.compilationUnit}") - throw ex - - def parse(using Context): Unit = monitor("parsing") { - val unit = ctx.compilationUnit - - unit.untpdTree = - if (unit.isJava) new JavaParser(unit.source).parse() - else { - val p = new Parser(unit.source) - // p.in.debugTokenStream = true - val tree = p.parse() - if (p.firstXmlPos.exists && !firstXmlPos.exists) - firstXmlPos = p.firstXmlPos - tree - } - - val printer = if (ctx.settings.Xprint.value.contains("parser")) default else typr - printer.println("parsed:\n" + unit.untpdTree.show) - if (Config.checkPositions) - unit.untpdTree.checkPos(nonOverlapping = !unit.isJava && !ctx.reporter.hasErrors) - } + // Run regardless of parsing errors + override def isRunnable(implicit ctx: Context): Boolean = true def enterSyms(using Context): Unit = monitor("indexing") { val unit = ctx.compilationUnit @@ -103,19 +80,26 @@ class FrontEnd extends Phase { override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = val unitContexts = for unit <- units yield - report.inform(s"compiling ${unit.source}") - ctx.fresh.setCompilationUnit(unit).withRootImports - unitContexts.foreach(parse(using _)) - record("parsedTrees", ast.Trees.ntrees) + val newCtx = ctx.fresh.setPhase(this.start).setCompilationUnit(unit) + report.inform(s"typing ${unit.source}") + if (addRootImports) + newCtx.withRootImports + else + newCtx + remaining = unitContexts while remaining.nonEmpty do enterSyms(using remaining.head) remaining = remaining.tail - - if firstXmlPos.exists && !defn.ScalaXmlPackageClass.exists then - report.error("""To support XML literals, your project must depend on scala-xml. - |See https://github.com/scala/scala-xml for more information.""".stripMargin, - firstXmlPos) + val firstXmlPos = ctx.base.parserPhase match { + case p: ParserPhase => + if p.firstXmlPos.exists && !defn.ScalaXmlPackageClass.exists then + report.error( + """To support XML literals, your project must depend on scala-xml. + |See https://github.com/scala/scala-xml for more information.""".stripMargin, + p.firstXmlPos) + case _ => + } unitContexts.foreach(typeCheck(using _)) record("total trees after typer", ast.Trees.ntrees) @@ -128,6 +112,12 @@ class FrontEnd extends Phase { def run(using Context): Unit = unsupported("run") } -object FrontEnd { +object TyperPhase { val name: String = "typer" } + +@deprecated(message = "FrontEnd has been split into TyperPhase and Parser. Refer to one or the other.") +object FrontEnd { + // For backwards compatibility: some plugins refer to FrontEnd so that they can schedule themselves after it. + val name: String = TyperPhase.name +} diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index eceac4c44952..b98e511d3864 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -14,6 +14,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.reporting.Diagnostic import dotty.tools.dotc.transform.{PostTyper, Staging} import dotty.tools.dotc.typer.ImportInfo._ +import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.{ParsedComment, SourceFile} import dotty.tools.dotc.{CompilationUnit, Compiler, Run} @@ -32,7 +33,7 @@ import scala.collection.mutable class ReplCompiler extends Compiler { override protected def frontendPhases: List[List[Phase]] = List( - List(new REPLFrontEnd), + List(new TyperPhase(addRootImports = false)), List(new CollectTopLevelImports), List(new PostTyper), ) diff --git a/compiler/src/dotty/tools/repl/ReplFrontEnd.scala b/compiler/src/dotty/tools/repl/ReplFrontEnd.scala deleted file mode 100644 index 4eb983e7f674..000000000000 --- a/compiler/src/dotty/tools/repl/ReplFrontEnd.scala +++ /dev/null @@ -1,26 +0,0 @@ -package dotty.tools -package repl - -import dotc.typer.FrontEnd -import dotc.CompilationUnit -import dotc.core.Contexts._ - -/** A customized `FrontEnd` for the REPL - * - * This customized front end does not perform parsing as part of its `runOn` - * method. This allows us to keep the parsing separate from the rest of the - * compiler pipeline. - */ -private[repl] class REPLFrontEnd extends FrontEnd { - - override def isRunnable(using Context): Boolean = true - - override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = { - assert(units.size == 1) // REPl runs one compilation unit at a time - val unit = units.head - val unitContext = ctx.fresh.setCompilationUnit(unit) - enterSyms(using unitContext) - typeCheck(using unitContext) - List(unit) - } -} diff --git a/docs/docs/internals/overall-structure.md b/docs/docs/internals/overall-structure.md index 874155589116..73964977ba42 100644 --- a/docs/docs/internals/overall-structure.md +++ b/docs/docs/internals/overall-structure.md @@ -97,7 +97,8 @@ phases. The current list of phases is specified in class [Compiler] as follows: /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = - List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer + List(new Parser) :: // scanner, parser + List(new TyperPhase) :: // namer, typer List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 12e52f941af5..f59133226d42 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -19,7 +19,7 @@ import scala.io.Codec import dotc._ import ast.{Trees, tpd, untpd} import core._, core.Decorators._ -import Comments._, Constants._, Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._ +import Comments._, Constants._, Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._, Periods._ import classpath.ClassPathEntries import reporting._ import typer.Typer @@ -306,7 +306,9 @@ class DottyLanguageServer extends LanguageServer val pos = sourcePosition(driver, uri, params.getPosition) val items = driver.compilationUnits.get(uri) match { - case Some(unit) => Completion.completions(pos)(using ctx.fresh.setCompilationUnit(unit))._2 + case Some(unit) => + val freshCtx = ctx.fresh.setPeriod(Period(ctx.runId, ctx.base.typerPhase.id)).setCompilationUnit(unit) + Completion.completions(pos)(using freshCtx)._2 case None => Nil }