scala.tools.nsc.interpreter.PresentationCompilation.scala Maven / Gradle / Ivy
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.tools.nsc.interpreter
import scala.collection.mutable
import scala.reflect.internal.util.{Position, RangePosition}
import scala.tools.nsc.ast.parser.Tokens
import scala.tools.nsc.backend.JavaPlatform
import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.{Settings, interactive}
import scala.tools.nsc.reporters.StoreReporter
import scala.tools.nsc.classpath._
import scala.tools.nsc.interpreter.Results.{Error, Result}
trait PresentationCompilation { self: IMain =>
private final val Cursor = IMain.DummyCursorFragment
/** Typecheck a line of REPL input, suitably wrapped with "interpreter wrapper" objects/classes, with the
* presentation compiler. The result of this method gives access to the typechecked tree and to autocompletion
* suggestions.
*
* The caller is responsible for calling [[PresentationCompileResult#cleanup]] to dispose of the compiler instance.
*/
def presentationCompile(cursor: Int, buf: String): Either[Result, PresentationCompilationResult] = {
if (global == null) Left(Error)
else {
val pc = newPresentationCompiler()
def cursorIsInKeyword(): Boolean = {
val scanner = pc.newUnitParser(buf).newScanner()
scanner.init()
while (scanner.token != Tokens.EOF) {
val token = scanner.token
val o = scanner.offset
scanner.nextToken()
if ((o to scanner.lastOffset).contains(cursor)) {
return (!Tokens.isIdentifier(token) && pc.syntaxAnalyzer.token2name.contains(token))
}
}
false
}
// Support completion of "def format = 42; for" by replacing the keyword with foo_CURSOR_ before
// typechecking. Only do this when needed to be able ot correctly return the type of `foo.bar`
// where `bar` is the complete name of a member.
val line1 = if (!cursorIsInKeyword()) buf else buf.patch(cursor, Cursor, 0)
val parserSource = pc.newSourceFile(line1)
val trees = pc.newUnitParser(new pc.CompilationUnit(parserSource)).parseStats() match {
case Nil => List(pc.EmptyTree)
case xs => xs
}
val importer = global.mkImporter(pc)
//println(s"pc: [[$line1]], <<${trees.size}>>")
val request = new Request(line1, trees map (t => importer.importTree(t)), parserSource, generousImports = true, storeResultInVal = false)
val origUnit = request.mkUnit
val unit = new pc.CompilationUnit(origUnit.source)
unit.body = pc.mkImporter(global).importTree(origUnit.body)
val richUnit = new pc.RichCompilationUnit(unit.source)
// disable brace patching in the parser, the snippet template isn't well-indented and the results can be surprising
pc.currentRun.parsing.withIncompleteHandler((pos, msg) => ()) {
pc.unitOfFile(richUnit.source.file) = richUnit
richUnit.body = unit.body
pc.enteringTyper(pc.typeCheck(richUnit))
}
val inputRange = pc.wrappingPos(trees)
// too bad dependent method types don't work for constructors
val result = new PresentationCompileResult(pc, inputRange, cursor, buf) { val unit = richUnit ; override val compiler: pc.type = pc }
Right(result)
}
}
/** Create an instance of the presentation compiler with a classpath comprising the REPL's configured classpath
* and the classes output by previously compiled REPL lines.
*
* You may directly interact with this compiler from any thread, although you must not access it concurrently
* from multiple threads.
*
* You may downcast the `reporter` to `StoreReporter` to access type errors.
*/
def newPresentationCompiler(): interactive.Global = {
def copySettings: Settings = {
val s = new Settings(_ => () /* ignores "bad option -nc" errors, etc */)
s.processArguments(global.settings.recreateArgs, processAll = false)
s.YpresentationAnyThread.value = true
s
}
val storeReporter: StoreReporter = new StoreReporter(copySettings)
val interactiveGlobal = new interactive.Global(copySettings, storeReporter) { self =>
def mergedFlatClasspath = {
val replOutClasspath = ClassPathFactory.newClassPath(replOutput.dir, settings, closeableRegistry)
AggregateClassPath(replOutClasspath :: global.platform.classPath :: Nil)
}
override lazy val platform: ThisPlatform = {
new JavaPlatform {
lazy val global: self.type = self
override lazy val classPath: ClassPath = mergedFlatClasspath
}
}
}
new interactiveGlobal.TyperRun()
interactiveGlobal
}
abstract class PresentationCompileResult(val compiler: interactive.Global, val inputRange: Position, val cursor: Int, val buf: String) extends PresentationCompilationResult {
val unit: compiler.RichCompilationUnit // depmet broken for constructors, can't be ctor arg
override def cleanup(): Unit = {
compiler.askShutdown()
}
import compiler.CompletionResult
def completionsAt(cursor: Int): CompletionResult = compiler.completionsAt(positionOf(cursor))
def positionOf(cursor: Int): Position =
unit.source.position(cursor)
def typedTreeAt(selectionStart: Int, selectionEnd: Int): compiler.Tree =
compiler.typedTreeAt(new RangePosition(unit.source, selectionStart, selectionStart, selectionEnd))
// offsets are 0-based
def treeAt(start: Int, end: Int): compiler.Tree = treeAt(unit.source.position(start).withEnd(end))
def treeAt(pos: Position): compiler.Tree = {
import compiler.{Locator, Template, Block}
new Locator(pos) locateIn unit.body match {
case t@Template(_, _, constructor :: (rest :+ last)) =>
if (rest.isEmpty) last
else Block(rest, last)
case t =>
t
}
}
def typeString(tree: compiler.Tree): String = {
tree.tpe match {
case null | compiler.NoType | compiler.ErrorType => ""
case tp if compiler.nme.isReplWrapperName(tp.typeSymbol.name) => ""
case tp => compiler.exitingTyper(tp.toString)
}
}
def treeString(tree: compiler.Tree): String =
compiler.showCode(tree)
override def print = {
val tree = treeAt(inputRange)
import compiler._
object makeCodePrinterPrintInferredTypes extends Transformer {
private def printableTypeTree(tp: Type): TypeTree = {
val tree = TypeTree(tp)
tree.wasEmpty = false
tree
}
override def transform(tree: Tree): Tree = super.transform(tree) match {
case ValDef(mods, name, tt @ build.SyntacticEmptyTypeTree(), rhs) =>
if (tree.symbol != null && tree.symbol != NoSymbol && nme.isReplWrapperName(tree.symbol.owner.name)) {
treeCopy.ValDef(tree, mods &~ (Flag.PRIVATE | Flag.LOCAL), name.dropLocal, printableTypeTree(tt.tpe), rhs)
} else {
treeCopy.ValDef(tree, mods, name, printableTypeTree(tt.tpe), rhs)
}
case DefDef(mods, name, tparams, vparamss, tt @ build.SyntacticEmptyTypeTree(), rhs) =>
treeCopy.DefDef(tree, mods, name, tparams, vparamss, printableTypeTree(tt.tpe), rhs)
case t => t
}
}
val tree1 = makeCodePrinterPrintInferredTypes.transform(tree)
val tpString = typeString(tree1) match {
case "" => ""
case s => " // : " + s
}
treeString(tree1) + tpString
}
override def typeAt(start: Int, end: Int) =
typeString(typedTreeAt(start, end))
val NoCandidates = (-1, Nil)
type Candidates = (Int, List[CompletionCandidate])
override def completionCandidates(filter: Boolean, tabCount: Int): Candidates = {
import compiler._
import CompletionResult.NoResults
def isMemberDeprecated(m: Member) = m match {
case tm: TypeMember if tm.viaView.isDeprecated || tm.viaView != NoSymbol && tm.viaView.owner.isDeprecated =>
true
case _ =>
m.sym.isDeprecated ||
m.sym.hasGetter && m.sym.getterIn(m.sym.owner).isDeprecated
}
def isMemberUniversal(m: Member) =
definitions.isUniversalMember(m.sym) ||
(m match {
case t: TypeMember =>
t.implicitlyAdded && t.viaView.info.params.head.info.bounds.isEmptyBounds
case _ =>
false
})
def memberArity(m: Member): CompletionCandidate.Arity =
if (m.sym.paramss.isEmpty) CompletionCandidate.Nullary
else if (m.sym.paramss.size == 1 && m.sym.paramss.head.isEmpty) CompletionCandidate.Nilary
else CompletionCandidate.Other
def defStringCandidates(matching: List[Member], isNew: Boolean): List[CompletionCandidate] = {
val seen = new mutable.HashSet[Symbol]()
val ccs = for {
member <- matching
if seen.add(member.sym)
sym <- if (member.sym.isClass && isNew) member.sym.info.decl(nme.CONSTRUCTOR).alternatives else member.sym.alternatives
sugared = sym.sugaredSymbolOrSelf
} yield {
CompletionCandidate(
name = member.symNameDropLocal.decoded,
arity = memberArity(member),
isDeprecated = isMemberDeprecated(member),
isUniversal = isMemberUniversal(member),
declString = () => {
if (sym.isPackageObjectOrClass) ""
else {
val tp = member.prefix memberType sym
val desc = Seq(if (isMemberDeprecated(member)) "(deprecated)" else "", if (isMemberUniversal(member)) "(universal)" else "")
val methodOtherDesc = if (!desc.exists(_ != "")) "" else " " + desc.filter(_ != "").mkString(" ")
sugared.defStringSeenAs(tp) + methodOtherDesc
}
},
alias = member.aliasInfo.fold[Option[String]](None)(s => Some(s.sym.nameString))
)
}
ccs
}
val found = this.completionsAt(cursor) match {
case NoResults => NoCandidates
case r =>
def shouldHide(m: Member): Boolean =
filter && tabCount == 0 && (isMemberDeprecated(m) || isMemberUniversal(m))
val matching = r.matchingResults(nameMatcher = if (filter) {entered => candidate => candidate.startsWith(entered)} else _ => _ => true).filterNot(shouldHide)
val pos1 = positionOf(cursor)
import compiler._
val locator = new Locator(pos1)
val tree = locator locateIn unit.body
var isNew = false
new TreeStackTraverser {
override def traverse(t: Tree): Unit = {
if (t eq tree) {
isNew = path.dropWhile { case _: Select | _: Annotated => true; case _ => false}.headOption match {
case Some(_: New) => true
case _ => false
}
} else super.traverse(t)
}
}.traverse(unit.body)
val candidates = defStringCandidates(matching, isNew)
val pos = cursor - r.positionDelta
(pos, candidates.sortBy(_.name))
}
found
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy