Skip to content

Commit

Permalink
Merge pull request #3960 from dotty-staging/ide-completions
Browse files Browse the repository at this point in the history
Context-sensitive IDE completions
  • Loading branch information
smarter authored Feb 12, 2018
2 parents e2f72cd + 4d0b9ed commit 5c4d7d2
Show file tree
Hide file tree
Showing 28 changed files with 681 additions and 334 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -896,8 +896,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma

def decls: List[Symbol] = tp.decls.toList

def members: List[Symbol] =
tp.memberDenots(takeAllFilter, (name, buf) => buf ++= tp.member(name).alternatives).map(_.symbol).toList
def members: List[Symbol] = tp.allMembers.map(_.symbol).toList

def typeSymbol: Symbol = tp.widenDealias.typeSymbol

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object CompilationUnit {
def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit =
mkCompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq()), unpickled, forceTrees)

/** Make a compilation unit the given unpickled tree */
/** Make a compilation unit, given picked bytes and unpickled tree */
def mkCompilationUnit(source: SourceFile, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = {
assert(!unpickled.isEmpty, unpickled)
val unit1 = new CompilationUnit(source)
Expand Down
38 changes: 23 additions & 15 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
(start.setRun(this) /: defn.RootImportFns)(addImport)
}

private[this] var compiling = false

private[this] var myCtx = rootContext(ictx)

/** The context created for this run */
Expand All @@ -72,8 +74,6 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
private[this] var myUnits: List[CompilationUnit] = _
private[this] var myUnitsCached: List[CompilationUnit] = _
private[this] var myFiles: Set[AbstractFile] = _
private[this] val myLateUnits = mutable.ListBuffer[CompilationUnit]()
private[this] var myLateFiles = mutable.Set[AbstractFile]()

/** The compilation units currently being compiled, this may return different
* results over time.
Expand All @@ -95,11 +95,11 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
myFiles
}

/** Units that are added from source completers but that are not compiled in current run. */
def lateUnits: List[CompilationUnit] = myLateUnits.toList
/** The source files of all late entered symbols, as a set */
private[this] var lateFiles = mutable.Set[AbstractFile]()

/** The source files of all late units, as a set */
def lateFiles: collection.Set[AbstractFile] = myLateFiles
/** Actions that need to be performed at the end of the current compilation run */
private[this] var finalizeActions = mutable.ListBuffer[() => Unit]()

def getSource(fileName: String): SourceFile = {
val f = new PlainFile(io.Path(fileName))
Expand Down Expand Up @@ -148,6 +148,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint

protected def compileUnits()(implicit ctx: Context) = Stats.maybeMonitored {
ctx.checkSingleThreaded()
compiling = true

// If testing pickler, make sure to stop after pickling phase:
val stopAfter =
Expand Down Expand Up @@ -189,24 +190,31 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
ctx.phases.foreach(_.initContext(runCtx))
runPhases(runCtx)
if (!ctx.reporter.hasErrors) Rewrites.writeBack()
while (finalizeActions.nonEmpty) {
val action = finalizeActions.remove(0)
action()
}
compiling = false
}

/** Enter top-level definitions of classes and objects contain in Scala source file `file`.
* The newly added symbols replace any previously entered symbols.
* If `typeCheck = true`, also run typer on the compilation unit.
*/
def enterRoots(file: AbstractFile)(implicit ctx: Context): Unit =
def lateCompile(file: AbstractFile, typeCheck: Boolean)(implicit ctx: Context): Unit =
if (!files.contains(file) && !lateFiles.contains(file)) {
lateFiles += file
val unit = new CompilationUnit(getSource(file.path))
myLateUnits += unit
myLateFiles += file
enterRoots(unit)(runContext.fresh.setCompilationUnit(unit))
def process()(implicit ctx: Context) = {
unit.untpdTree = new Parser(unit.source).parse()
ctx.typer.lateEnter(unit.untpdTree)
def typeCheckUnit() = unit.tpdTree = ctx.typer.typedExpr(unit.untpdTree)
if (typeCheck)
if (compiling) finalizeActions += (() => typeCheckUnit()) else typeCheckUnit()
}
process()(runContext.fresh.setCompilationUnit(unit))
}

private def enterRoots(unit: CompilationUnit)(implicit ctx: Context): Unit = {
unit.untpdTree = new Parser(unit.source).parse()
ctx.typer.lateEnter(unit.untpdTree)
}

private sealed trait PrintedTree
private /*final*/ case class SomePrintedTree(phase: String, tree: String) extends PrintedTree
private object NoPrintedTree extends PrintedTree
Expand Down
21 changes: 21 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,27 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def tpes: List[Type] = xs map (_.tpe)
}

/** A trait for loaders that compute trees. Currently implemented just by DottyUnpickler. */
trait TreeProvider {
protected def computeTrees(implicit ctx: Context): List[Tree]

private[this] var myTrees: List[Tree] = null

/** Get trees defined by this provider. Cache them if -Yretain-trees is set. */
def trees(implicit ctx: Context): List[Tree] =
if (ctx.settings.YretainTrees.value) {
if (myTrees == null) myTrees = computeTrees
myTrees
} else computeTrees

/** Get first tree defined by this provider, or EmptyTree if none exists */
def tree(implicit ctx: Context): Tree =
trees.headOption.getOrElse(EmptyTree)

/** Is it possible that the tree to load contains a definition of or reference to `id`? */
def mightContain(id: String)(implicit ctx: Context) = true
}

// convert a numeric with a toXXX method
def primitiveConversion(tree: Tree, numericCls: Symbol)(implicit ctx: Context): Tree = {
val mname = ("to" + numericCls.name).toTermName
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ object Printers {
val cyclicErrors: Printer = noPrinter
val dottydoc: Printer = noPrinter
val exhaustivity: Printer = noPrinter
val incremental: Printer = noPrinter
val gadts: Printer = noPrinter
val hk: Printer = noPrinter
val implicits: Printer = noPrinter
val implicitsDetailed: Printer = noPrinter
val inlining: Printer = noPrinter
val interactiv: Printer = noPrinter
val overload: Printer = noPrinter
val patmatch: Printer = noPrinter
val pickling: Printer = noPrinter
Expand Down
10 changes: 9 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,29 @@ object Annotations {

abstract class Annotation {
def tree(implicit ctx: Context): Tree

def symbol(implicit ctx: Context): Symbol =
if (tree.symbol.isConstructor) tree.symbol.owner
else tree.tpe.typeSymbol

def matches(cls: Symbol)(implicit ctx: Context): Boolean = symbol.derivesFrom(cls)

def appliesToModule: Boolean = true // for now; see remark in SymDenotations

def derivedAnnotation(tree: Tree)(implicit ctx: Context) =
if (tree eq this.tree) this else Annotation(tree)

def arguments(implicit ctx: Context) = ast.tpd.arguments(tree)

def argument(i: Int)(implicit ctx: Context): Option[Tree] = {
val args = arguments
if (i < args.length) Some(args(i)) else None
}
def argumentConstant(i: Int)(implicit ctx: Context): Option[Constant] =
for (ConstantType(c) <- argument(i) map (_.tpe)) yield c

def isEvaluated: Boolean = true

def ensureCompleted(implicit ctx: Context): Unit = tree
}

Expand All @@ -43,6 +49,8 @@ object Annotations {
if (myTree == null) myTree = complete(ctx)
myTree
}

override def isEvaluated = myTree != null
}

/** An annotation indicating the body of a right-hand side,
Expand Down Expand Up @@ -73,7 +81,7 @@ object Annotations {
}
myBody
}
def isEvaluated = evaluated
override def isEvaluated = evaluated
}

object Annotation {
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ object Contexts {
private[this] var _typeAssigner: TypeAssigner = _
protected def typeAssigner_=(typeAssigner: TypeAssigner) = _typeAssigner = typeAssigner
def typeAssigner: TypeAssigner = _typeAssigner
def typer: Typer = _typeAssigner.asInstanceOf[Typer]

/** The currently active import info */
private[this] var _importInfo: ImportInfo = _
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,9 @@ object Decorators {
def hl(args: Any*)(implicit ctx: Context): String =
new SyntaxFormatter(sc).assemble(args).stripMargin
}

implicit class ArrayInterpolator[T <: AnyRef](val arr: Array[T]) extends AnyVal {
def binarySearch(x: T): Int = java.util.Arrays.binarySearch(arr.asInstanceOf[Array[Object]], x)
}
}

5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -789,10 +789,7 @@ object Denotations {
this match {
case symd: SymDenotation =>
if (ctx.stillValid(symd)) return updateValidity()
if (ctx.acceptStale(symd)) {
val newd = symd.owner.info.decls.lookup(symd.name)
return (newd.denot: SingleDenotation).orElse(symd).updateValidity()
}
if (ctx.acceptStale(symd)) return symd.currentSymbol.denot.orElse(symd).updateValidity()
case _ =>
}
if (!symbol.exists) return updateValidity()
Expand Down
21 changes: 18 additions & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import util.SimpleIdentityMap
import util.Stats
import java.util.WeakHashMap
import config.Config
import config.Printers.{incremental, noPrinter}
import config.Printers.noPrinter
import reporting.diagnostic.Message
import reporting.diagnostic.messages.BadSymbolicReference
import reporting.trace
Expand Down Expand Up @@ -206,7 +206,7 @@ object SymDenotations {
* Uncompleted denotations set myInfo to a LazyType.
*/
final def info(implicit ctx: Context): Type = {
def completeInfo = {
def completeInfo = { // Written this way so that `info` is small enough to be inlined
completeFrom(myInfo.asInstanceOf[LazyType]); info
}
if (myInfo.isInstanceOf[LazyType]) completeInfo else myInfo
Expand Down Expand Up @@ -959,7 +959,6 @@ object SymDenotations {
}
}


/** The class with the same (type-) name as this module or module class,
* and which is also defined in the same scope and compilation unit.
* NoSymbol if this class does not exist.
Expand Down Expand Up @@ -1135,6 +1134,22 @@ object SymDenotations {
/** The primary constructor of a class or trait, NoSymbol if not applicable. */
def primaryConstructor(implicit ctx: Context): Symbol = NoSymbol

/** The current declaration in this symbol's class owner that has the same name
* as this one, and, if there are several, also has the same signature.
*/
def currentSymbol(implicit ctx: Context): Symbol = {
val candidates = owner.info.decls.lookupAll(name)
def test(sym: Symbol): Symbol =
if (sym == symbol || sym.signature == signature) sym
else if (candidates.hasNext) test(candidates.next)
else NoSymbol
if (candidates.hasNext) {
val sym = candidates.next
if (candidates.hasNext) test(sym) else sym
}
else NoSymbol
}

// ----- type-related ------------------------------------------------

/** The type parameters of a class symbol, Nil for all other symbols */
Expand Down
28 changes: 16 additions & 12 deletions compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import util.Stats
import Decorators._
import scala.util.control.NonFatal
import ast.Trees._
import ast.tpd
import parsing.Parsers.OutlineParser
import reporting.trace

Expand Down Expand Up @@ -118,7 +119,9 @@ class SymbolLoaders {
scope: Scope = EmptyScope)(implicit ctx: Context): Unit = {

val completer = new SourcefileLoader(src)
if (ctx.settings.scansource.value) {
if (ctx.settings.scansource.value && ctx.run != null) {
System.out.print(i"scanning $src ...")
System.out.flush()
if (src.exists && !src.isDirectory) {
val filePath = owner.ownersIterator.takeWhile(!_.isRoot).map(_.name.toTermName).toList

Expand Down Expand Up @@ -160,6 +163,7 @@ class SymbolLoaders {

val unit = new CompilationUnit(ctx.run.getSource(src.path))
enterScanned(unit)(ctx.run.runContext.fresh.setCompilationUnit(unit))
System.out.println(" done")
}
}
else enterClassAndModule(owner, name, completer, scope = scope)
Expand Down Expand Up @@ -338,15 +342,8 @@ abstract class SymbolLoader extends LazyType {
postProcess(root.scalacLinkedClass.denot)
}
}
}

class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {

override def sourceFileOrNull: AbstractFile = classfile

def description(implicit ctx: Context) = "class file " + classfile.toString

def rootDenots(rootDenot: ClassDenotation)(implicit ctx: Context): (ClassDenotation, ClassDenotation) = {
protected def rootDenots(rootDenot: ClassDenotation)(implicit ctx: Context): (ClassDenotation, ClassDenotation) = {
val linkedDenot = rootDenot.scalacLinkedClass.denot match {
case d: ClassDenotation => d
case d =>
Expand All @@ -368,6 +365,13 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
if (rootDenot is ModuleClass) (linkedDenot, rootDenot)
else (rootDenot, linkedDenot)
}
}

class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {

override def sourceFileOrNull: AbstractFile = classfile

def description(implicit ctx: Context) = "class file " + classfile.toString

override def doComplete(root: SymDenotation)(implicit ctx: Context): Unit =
load(root)
Expand All @@ -379,8 +383,8 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
if (mayLoadTreesFromTasty) {
result match {
case Some(unpickler: tasty.DottyUnpickler) =>
classRoot.symbol.asClass.unpickler = unpickler
moduleRoot.symbol.asClass.unpickler = unpickler
classRoot.classSymbol.treeOrProvider = unpickler
moduleRoot.classSymbol.treeOrProvider = unpickler
case _ =>
}
}
Expand All @@ -394,5 +398,5 @@ class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader {
def description(implicit ctx: Context) = "source file " + srcfile.toString
override def sourceFileOrNull = srcfile
def doComplete(root: SymDenotation)(implicit ctx: Context): Unit =
ctx.run.enterRoots(srcfile)
ctx.run.lateCompile(srcfile, typeCheck = ctx.settings.YretainTrees.value)
}
Loading

0 comments on commit 5c4d7d2

Please sign in to comment.