diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 6bd123c51f9..653c0d5be41 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -21,6 +21,27 @@ trait JavaPlatform extends Platform { private[nsc] var currentClassPath: Option[MergedClassPath[AbstractFile]] = None + val forceIndexPackages: collection.mutable.Set[String] = collection.mutable.Set.empty + val classPathIndexByClassName: collection.mutable.Map[String, Set[String]] = collection.mutable.Map.empty + + def addForceIndexPackages(packages: Set[String]) = { + forceIndexPackages ++= packages + } + + def updateIndex() = { + def addIfInitialized(pkg: Symbol): Unit = { + if (pkg.hasCompleteInfo || forceIndexPackages.exists(p => p startsWith pkg.fullName)) { + for (m <- pkg.info.members if m != pkg) { // _root_ is member of _root_ it seems, so filter m != pkg + if (m.isClass) + classPathIndexByClassName(m.name.toString) = classPathIndexByClassName.getOrElse(m.name.toString, Set.empty) + m.fullName + if (m.moduleClass.isPackageClass) + addIfInitialized(m.moduleClass) + } + } + } + addIfInitialized(rootMirror.getPackage("_root_": TermName)) + } + def classPath: ClassPath[AbstractFile] = { assert(settings.YclasspathImpl.value == ClassPathRepresentationType.Recursive, "To use recursive classpath representation you must enable it with -YclasspathImpl:recursive compiler option.") diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index c80aaea160a..d16ed4defca 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -245,7 +245,15 @@ trait ContextErrors { NormalTypeError(tree, "reference to " + name + " is ambiguous;\n" + msg) def SymbolNotFoundError(tree: Tree, name: Name, owner: Symbol, startingIdentCx: Context) = { - NormalTypeError(tree, "not found: "+decodeWithKind(name, owner)) + platform.updateIndex() + lazy val candidatesString = name match { + case tn: TypeName => + val cs = platform.classPathIndexByClassName.getOrElse(tn.toString, Set.empty) + if (cs.nonEmpty) cs.mkString(". You may want to import one of the following classes from your classpath:\n ", "\n ", "") + else "" + case _ => "" + } + NormalTypeError(tree, "not found: "+decodeWithKind(name, owner) + candidatesString) } // typedAppliedTypeTree diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 09f66fbae87..3a55b84319d 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -64,6 +64,8 @@ import java.io.File class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Settings, protected val out: JPrintWriter) extends AbstractScriptEngine with Compilable with Imports with PresentationCompilation { imain => + val api = new PresentationCompilerAPI(this) + setBindings(createBindings, ScriptContext.ENGINE_SCOPE) object replOutput extends ReplOutput(settings.Yreploutdir) { } @@ -1020,6 +1022,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set import interactiveGlobal._ val run = new TyperRun() val unit = new RichCompilationUnit(newCompilationUnit(code).source) + unitOfFile(unit.source.file) = unit typeCheck(unit) PresentationCompileResult(this, interactiveGlobal)(unit, ObjectSourceCode.preambleLength) } diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerAPI.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerAPI.scala new file mode 100644 index 00000000000..0394216bc64 --- /dev/null +++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerAPI.scala @@ -0,0 +1,26 @@ +package scala.tools.nsc.interpreter + +import scala.reflect.internal.util.RangePosition +import scala.tools.nsc.typechecker.TypeStrings + +class PresentationCompilerAPI(interpreter: IMain) { + /** + * Index the top-level classes in the the provided packages for the `not found: Class` error + * message. + */ + def scanPackagesForClassNotFoundMessage(packages: Set[String]): Unit = { + interpreter.global.platform.addForceIndexPackages(packages) + } + + def typeAt(code: String, selectionStart: Int, selectionEnd: Int): Option[String] = { + interpreter.presentationCompile(code, synthetic = false) match { + case Left(_) => None + case Right(result) => + val start = selectionStart + result.preambleLength + val end = selectionEnd + result.preambleLength + val pos = new RangePosition(result.unit.source, start, start, end) + val tree = result.compiler.typedTreeAt(pos) + Some(interpreter.global.exitingTyper(tree.tpe.toString)) + } + } +} diff --git a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala index 356c07e8517..3699a18622a 100644 --- a/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala +++ b/src/repl/scala/tools/nsc/interpreter/PresentationCompilerCompleter.scala @@ -11,6 +11,13 @@ class PresentationCompilerCompleter(intp: IMain) extends ScalaCompleter { val request = new Request(buf, cursor) if (request == lastRequest) tabCount += 1 else { tabCount = 0; lastRequest = request} val printMode = buf.matches(""".*// *print *$""") && cursor == buf.length + val (typeAtMode, start, end) = { + val R = """.*// *typeAt *(\d+) *(\d+) *$""".r + buf match { + case R(s, e) if cursor == buf.length => (true, s.toInt, e.toInt) + case _ => (false, -1, -1) + } + } intp.presentationCompile(buf) match { case Left(_) => Completion.NoCandidates case Right(result) => try { @@ -24,6 +31,9 @@ class PresentationCompilerCompleter(intp: IMain) extends ScalaCompleter { } val printed = showCode(tree) Candidates(cursor, "" :: printed :: Nil) + } else if (typeAtMode) { + val tp = intp.api.typeAt(buf, start, end) + Candidates(cursor, "" :: tp ++: Nil) } else { import result.CompletionResult._ result.completionsOf(buf, cursor) match {