Skip to content

Commit 1590d2b

Browse files
committed
Introduce best-effort compilation for IDEs
2 new experimental options are introduces for the compiler: `-Ybest-effort-dir` and `-Ywith-best-effort-tasty`. A related Best Effort TASTy format, a TASTy aligned file format able to hold some errored trees. Behaviour of the options and the format is documented as part of this commit in the `best-effort-compilation.md` docs file.
1 parent 62479c9 commit 1590d2b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+817
-163
lines changed

compiler/src/dotty/tools/backend/jvm/GenBCode.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class GenBCode extends Phase { self =>
1616

1717
override def description: String = GenBCode.description
1818

19+
override def isRunnable(using Context) = super.isRunnable && !ctx.usesBestEffortTasty
20+
1921
private val superCallsMap = new MutableSymbolMap[Set[ClassSymbol]]
2022
def registerSuperCall(sym: Symbol, calls: ClassSymbol): Unit = {
2123
val old = superCallsMap.getOrElse(sym, Set.empty)

compiler/src/dotty/tools/backend/sjs/GenSJSIR.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class GenSJSIR extends Phase {
1212
override def description: String = GenSJSIR.description
1313

1414
override def isRunnable(using Context): Boolean =
15-
super.isRunnable && ctx.settings.scalajs.value
15+
super.isRunnable && ctx.settings.scalajs.value && !ctx.usesBestEffortTasty
1616

1717
def run(using Context): Unit =
1818
new JSCodeGen().run()

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ class Driver {
100100
None
101101
else file.extension match
102102
case "jar" => Some(file.path)
103-
case "tasty" =>
104-
TastyFileUtil.getClassPath(file) match
103+
case "tasty" | "betasty" =>
104+
TastyFileUtil.getClassPath(file, ctx.withBestEffortTasty) match
105105
case Some(classpath) => Some(classpath)
106106
case _ =>
107107
report.error(em"Could not load classname from: ${file.path}")

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,13 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
225225
if (ctx.settings.YtestPickler.value) List("pickler")
226226
else ctx.settings.YstopAfter.value
227227

228+
var forceReachPhaseMaybe =
229+
if (ctx.isBestEffort) Some("typer")
230+
else None
231+
232+
var reachedSemanticDB = false
233+
var reachedPickler = false
234+
228235
val pluginPlan = ctx.base.addPluginPhases(ctx.base.phasePlan)
229236
val phases = ctx.base.fusePhases(pluginPlan,
230237
ctx.settings.Yskip.value, ctx.settings.YstopBefore.value, stopAfter, ctx.settings.Ycheck.value)
@@ -239,7 +246,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
239246
var phasesWereAdjusted = false
240247

241248
for (phase <- ctx.base.allPhases)
242-
if (phase.isRunnable)
249+
if (phase.isRunnable || forceReachPhaseMaybe.nonEmpty)
243250
Stats.trackTime(s"$phase ms ") {
244251
val start = System.currentTimeMillis
245252
val profileBefore = profiler.beforePhase(phase)
@@ -249,6 +256,24 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
249256
for (unit <- units)
250257
lastPrintedTree =
251258
printTree(lastPrintedTree)(using ctx.fresh.setPhase(phase.next).setCompilationUnit(unit))
259+
260+
forceReachPhaseMaybe match {
261+
case Some(forceReachPhase) if phase.phaseName == forceReachPhase =>
262+
forceReachPhaseMaybe = None
263+
case _ =>
264+
}
265+
266+
if phase.phaseName == "extractSemanticDB" then reachedSemanticDB = true
267+
if phase.phaseName == "pickler" then reachedPickler = true
268+
269+
if !reachedSemanticDB && forceReachPhaseMaybe.isEmpty && ctx.reporter.hasErrors && ctx.isBestEffort then
270+
ctx.base.allPhases.find(_.phaseName == "extractSemanticDB").foreach(_.runOn(units))
271+
reachedSemanticDB = true
272+
273+
if !reachedPickler && forceReachPhaseMaybe.isEmpty && ctx.reporter.hasErrors && ctx.isBestEffort then
274+
ctx.base.allPhases.find(_.phaseName == "pickler").foreach(_.runOn(units))
275+
reachedPickler = true
276+
252277
report.informTime(s"$phase ", start)
253278
Stats.record(s"total trees at end of $phase", ast.Trees.ntrees)
254279
for (unit <- units)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,12 +886,12 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
886886
else cpy.PackageDef(tree)(pid, slicedStats) :: Nil
887887
case tdef: TypeDef =>
888888
val sym = tdef.symbol
889-
assert(sym.isClass)
889+
if !ctx.isBestEffort then assert(sym.isClass)
890890
if (cls == sym || cls == sym.linkedClass) tdef :: Nil
891891
else Nil
892892
case vdef: ValDef =>
893893
val sym = vdef.symbol
894-
assert(sym.is(Module))
894+
if !ctx.isBestEffort then assert(sym.is(Module))
895895
if (cls == sym.companionClass || cls == sym.moduleClass) vdef :: Nil
896896
else Nil
897897
case tree =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
4848
case _: RefTree | _: GenericApply | _: Inlined | _: Hole =>
4949
ta.assignType(untpd.Apply(fn, args), fn, args)
5050
case _ =>
51-
assert(ctx.reporter.errorsReported)
51+
assert(ctx.isBestEffort || ctx.usesBestEffortTasty || ctx.reporter.errorsReported)
5252
ta.assignType(untpd.Apply(fn, args), fn, args)
5353

5454
def TypeApply(fn: Tree, args: List[Tree])(using Context): TypeApply = fn match
@@ -57,7 +57,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
5757
case _: RefTree | _: GenericApply =>
5858
ta.assignType(untpd.TypeApply(fn, args), fn, args)
5959
case _ =>
60-
assert(ctx.reporter.errorsReported)
60+
assert(ctx.isBestEffort || ctx.usesBestEffortTasty || ctx.reporter.errorsReported)
6161
ta.assignType(untpd.TypeApply(fn, args), fn, args)
6262

6363
def Literal(const: Constant)(using Context): Literal =

compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
286286
}
287287

288288
protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file)
289-
protected def isMatchingFile(f: JFile): Boolean = f.isClass
289+
protected def isMatchingFile(f: JFile): Boolean = f.isClass || f.isBestEffortTasty
290290

291291
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = files(inPackage)
292292
}

compiler/src/dotty/tools/dotc/classpath/FileUtils.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ object FileUtils {
3737

3838
def isClass: Boolean = file.isFile && file.getName.endsWith(".class") && !file.getName.endsWith("$class.class")
3939
// FIXME: drop last condition when we stop being compatible with Scala 2.11
40+
41+
def isTasty: Boolean = file.isFile && file.getName.endsWith(".tasty")
42+
43+
def isBestEffortTasty: Boolean = file.isFile && file.getName.endsWith(".betasty")
4044
}
4145

4246
private val SUFFIX_CLASS = ".class"

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ private sealed trait YSettings:
376376
//.withPostSetHook( _ => YprofileEnabled.value = true )
377377
val YprofileRunGcBetweenPhases: Setting[List[String]] = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_")
378378
//.withPostSetHook( _ => YprofileEnabled.value = true )
379+
380+
val YbestEffortDir: Setting[String] = StringSetting("-Ybest-effort-dir", "dir", "Enable best-effort compilation attempting to produce tasty in case of failure to specified directory, as part of the pickler phase.", "")
381+
val YwithBestEffortTasty: Setting[Boolean] = BooleanSetting("-Ywith-best-effort-tasty", "Allow to compile from a best effort tasty files. If such file is used, the compiler will stop after the pickler phase.")
379382

380383
// Experimental language features
381384
val YnoKindPolymorphism: Setting[Boolean] = BooleanSetting("-Yno-kind-polymorphism", "Disable kind polymorphism.")

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,18 @@ object Contexts {
453453
/** Is the explicit nulls option set? */
454454
def explicitNulls: Boolean = base.settings.YexplicitNulls.value
455455

456+
/** Is best-effort-dir option set? */
457+
def isBestEffort: Boolean = base.settings.YbestEffortDir.value.nonEmpty
458+
459+
/** Is the from-best-effort-tasty option set to true? */
460+
def withBestEffortTasty: Boolean = base.settings.YwithBestEffortTasty.value
461+
462+
/** Were any best effort tasty dependencies used during compilation? */
463+
def usesBestEffortTasty: Boolean = base.usedBestEffortTasty
464+
465+
/** Confirm that a best effort tasty dependency was used during compilation. */
466+
def setUsesBestEffortTasty(): Unit = base.usedBestEffortTasty = true
467+
456468
/** A fresh clone of this context embedded in this context. */
457469
def fresh: FreshContext = freshOver(this)
458470

@@ -939,6 +951,9 @@ object Contexts {
939951
val sources: util.HashMap[AbstractFile, SourceFile] = util.HashMap[AbstractFile, SourceFile]()
940952
val files: util.HashMap[TermName, AbstractFile] = util.HashMap()
941953

954+
/** Was best effort file used during compilation? */
955+
private[core] var usedBestEffortTasty = false
956+
942957
// Types state
943958
/** A table for hash consing unique types */
944959
private[core] val uniques: Uniques = Uniques()

0 commit comments

Comments
 (0)