Skip to content

Commit b2a61c1

Browse files
committed
Add ClassSymbol#tree to get the tree of a top-level class
When typechecking a class from source or when unpickling it from tasty, we normally do not keep a reference to the class tree, but there's a wealth of information only present in the tree that we may wish to use. In an IDE for example, this makes it easy to provide features like "jump to definition". This commit unlocks this potential by adding a `ClassSymbol#tree` method that, when used together with the `-Yretain-trees` flag, returns the tree corresponding to a top-level class. This also replaces the `unpicklers` map in `CompilationUnit` by `ClassSymbol#unpickler`.
1 parent 30bd0a7 commit b2a61c1

File tree

7 files changed

+54
-12
lines changed

7 files changed

+54
-12
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,4 @@ class CompilationUnit(val source: SourceFile) {
1919

2020
/** Pickled TASTY binaries, indexed by class. */
2121
var pickled: Map[ClassSymbol, Array[Byte]] = Map()
22-
23-
var unpicklers: Map[ClassSymbol, TastyUnpickler] = Map()
2422
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,15 @@ object FromTasty extends Driver {
8383
case clsd: ClassDenotation =>
8484
clsd.infoOrCompleter match {
8585
case info: ClassfileLoader =>
86-
info.load(clsd) match {
87-
case Some(unpickler: DottyUnpickler) =>
88-
val List(unpickled) = unpickler.body(ctx.addMode(Mode.ReadPositions))
86+
info.load(clsd)
87+
val unpickled = clsd.symbol.asClass.tree
88+
if (unpickled != null) {
8989
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq()))
9090
unit1.tpdTree = unpickled
91-
unit1.unpicklers += (clsd.classSymbol -> unpickler.unpickler)
9291
force.traverse(unit1.tpdTree)
9392
unit1
94-
case _ =>
93+
} else
9594
cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute")
96-
}
9795
case info =>
9896
cannotUnpickle(s"its info of type ${info.getClass} is not a ClassfileLoader")
9997
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class ScalaSettings extends Settings.SettingGroup {
9898
val YforceSbtPhases = BooleanSetting("-Yforce-sbt-phases", "Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging.")
9999
val YdumpSbtInc = BooleanSetting("-Ydump-sbt-inc", "For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases.")
100100
val YcheckAllPatmat = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm)")
101+
val YretainTrees = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree")
101102

102103
/** Area-specific debug output */
103104
val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Decorators.{PreNamedString, StringInterpolators}
1717
import classfile.ClassfileParser
1818
import util.Stats
1919
import scala.util.control.NonFatal
20+
import ast.Trees._
2021

2122
object SymbolLoaders {
2223
/** A marker trait for a completer that replaces the original
@@ -320,9 +321,14 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
320321
override def doComplete(root: SymDenotation)(implicit ctx: Context): Unit =
321322
load(root)
322323

323-
def load(root: SymDenotation)(implicit ctx: Context): Option[ClassfileParser.Embedded] = {
324+
def load(root: SymDenotation)(implicit ctx: Context): Unit = {
324325
val (classRoot, moduleRoot) = rootDenots(root.asClass)
325-
new ClassfileParser(classfile, classRoot, moduleRoot)(ctx).run()
326+
(new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)).run() match {
327+
case Some(unpickler: tasty.DottyUnpickler) if ctx.settings.YretainTrees.value =>
328+
classRoot.symbol.asClass.unpickler = unpickler
329+
moduleRoot.symbol.asClass.unpickler = unpickler
330+
case _ =>
331+
}
326332
}
327333
}
328334

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import DenotTransformers._
2020
import StdNames._
2121
import NameOps._
2222
import NameKinds.LazyImplicitName
23-
import ast.tpd.Tree
23+
import ast.tpd
24+
import tpd.Tree
2425
import ast.TreeTypeMap
2526
import Constants.Constant
2627
import reporting.diagnostic.Message
@@ -552,6 +553,34 @@ object Symbols {
552553

553554
type ThisName = TypeName
554555

556+
/** If this is a top-level class, and if `-Yretain-trees` is set, return the TypeDef tree
557+
* for this class, otherwise EmptyTree.
558+
*/
559+
def tree(implicit ctx: Context): tpd.Tree /* tpd.TypeDef | tpd.EmptyTree */ = {
560+
// TODO: Consider storing this tree like we store lazy trees for inline functions
561+
if (unpickler != null && !denot.isAbsent) {
562+
assert(myTree.isEmpty)
563+
564+
import ast.Trees._
565+
566+
def findTree(tree: tpd.Tree): Option[tpd.TypeDef] = tree match {
567+
case PackageDef(_, stats) =>
568+
stats.flatMap(findTree).headOption
569+
case tree: tpd.TypeDef if tree.symbol == this =>
570+
Some(tree)
571+
case _ =>
572+
None
573+
}
574+
val List(unpickledTree) = unpickler.body(ctx.addMode(Mode.ReadPositions))
575+
unpickler = null
576+
577+
myTree = findTree(unpickledTree).get
578+
}
579+
myTree
580+
}
581+
private[dotc] var myTree: tpd.Tree = tpd.EmptyTree
582+
private[dotc] var unpickler: tasty.DottyUnpickler = _
583+
555584
/** The source or class file from which this class was generated, null if not applicable. */
556585
override def associatedFile(implicit ctx: Context): AbstractFile =
557586
if (assocFile != null || (this.owner is PackageClass) || this.isEffectiveRoot) assocFile

compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,15 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded {
4646
def enter(roots: Set[SymDenotation])(implicit ctx: Context): Unit =
4747
treeUnpickler.enterTopLevel(roots)
4848

49+
/** Only used if `-Yretain-trees` is set. */
50+
private[this] var myBody: List[Tree] = _
4951
/** The unpickled trees, and the source file they come from. */
5052
def body(implicit ctx: Context): List[Tree] = {
51-
treeUnpickler.unpickle()
53+
def computeBody() = treeUnpickler.unpickle()
54+
if (ctx.settings.YretainTrees.value) {
55+
if (myBody == null)
56+
myBody = computeBody()
57+
myBody
58+
} else computeBody()
5259
}
5360
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
13411341
// check value class constraints
13421342
checkDerivedValueClass(cls, body1)
13431343

1344+
if (ctx.settings.YretainTrees.value) {
1345+
cls.myTree = cdef1
1346+
}
13441347
cdef1
13451348

13461349
// todo later: check that

0 commit comments

Comments
 (0)