Skip to content

IDE support for Dotty via the Language Server Protocol, including a Visual Studio Code extension #2532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 26, 2017

Conversation

smarter
Copy link
Member

@smarter smarter commented May 25, 2017

This PR introduces IDE support to Dotty, this requires several components (see each individual commit messages for more detailed explanations):

  • A set of experimental APIs in the compiler designed for IDE usage: dotty.tools.dotc.interactive.*. These APIs were developed for the Dotty Language Server but could evolve to support other usages like the REPL or other IDEs.
  • An implementation of the Language Server Protocol for Dotty, this allow any editor supporting the LSP to get great Dotty support with a minimal amount of work (see http://langserver.org/ for a list of LSP implementations).
  • A Visual Studio Code extension to run the Dotty Language Server.
  • New commands in the sbt-dotty plugin to setup everything needed to run the Dotty Language Server and start Visual Studio Code.

Quick start

  1. Install Visual Studio Code
  2. In your own project compiled with dotty, make sure sbt-dotty 0.1.0-RC5 is used, and run:
sbt launchIDE

Status

Fully supported features:

  • Typechecking as you type to show compiler errors/warnings
  • Type information on hover
  • Go to definition (in the current project)
  • Find all references

Partially working features:

  • Completion
  • Renaming
  • Go to definition in external projects

Unimplemented features:

  • Documentation on hover
  • Formatting code (requires integrating with scalafmt)
  • Quick fixes (probably by integrating with scalafix)

Current limitations, to be fixed:

  • Projects should be compiled with sbt before starting the IDE, this is
    automatically done for you if you run sbt launchIDE.
  • Once the IDE is started, source files that are not opened in the IDE
    should not be modified in some other editor, the IDE won't pick up
    these changes.
  • Not all compiler errors/warnings are displayed, just those occuring
    during typechecking.

Future

There is much more that could be done, in particular see https://github.com/lampepfl/dotty/issues/1523 for a lot of potential improvements that we should keep in mind.

@smarter
Copy link
Member Author

smarter commented May 25, 2017

/cc @fommil @dragos @sschaef

@smarter smarter requested review from odersky and felixmulder May 25, 2017 18:29
@fommil
Copy link

fommil commented May 26, 2017

nice! btw, we're going to add language server to ensime. This will serve as an excellent reference implementation for ensime 3.0. Hmm, I guess scastie might need to use this for dotty support.

Copy link
Contributor

@felixmulder felixmulder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that cancelToken is not used anywhere, I assume we'd need to implement safe points in the compiler to use that.

Let's remember to open an issue for that once this is merged.

Otherwise LGTM, happy to have this in!

classRoot.symbol.asClass.unpickler = unpickler
moduleRoot.symbol.asClass.unpickler = unpickler
case _ =>
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason behind making this non-RT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by RT here? Referentially transparent? This method is never called multiple times on the same denotation.

*/
@tailrec def sourceSymbol(sym: Symbol)(implicit ctx: Context): Symbol =
if (!sym.exists)
sym
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=> NoSymbol ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's completely equivalent, do you want me to change this for style reasons?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave that up to you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either works fine.


private var myCtx: Context = myInitCtx

def currentCtx: Context = myCtx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be public?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, very much so, see usage in DottyLanguageServer

// Like in `ZipArchiveFileLookup` we assume that zips are immutable
private val zipClassPathClasses: Seq[String] = zipClassPaths.flatMap { zipCp =>
// (new ZipFile(zipCp.asInstanceOf[ZipArchiveFileLookup[_]].zipFile).stream.collect(Collectors.toList()).asScala: List[ZipEntry])
// Working with Java 8 stream without SAMs and scala-java8-compat is awful.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

private val myOpenedTrees = new mutable.LinkedHashMap[URI, List[SourceTree]]

def openedFiles: Map[URI, SourceFile] = myOpenedFiles
def openedTrees: Map[URI, List[SourceTree]] = myOpenedTrees
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see usage in DottyLanguageServer

}

override def didChangeConfiguration(params: DidChangeConfigurationParams): Unit =
/*thisServer.synchronized*/ {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a docstring here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A docstring for what? If you're curious what didChangeConfiguration is for, see the spec: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didchangeconfiguration-notification
The commented synchronized is so that if we ever implement this method, we don't forget to make it synchronized

/*thisServer.synchronized*/ {}

override def didChangeWatchedFiles(params: DidChangeWatchedFilesParams): Unit =
/*thisServer.synchronized*/ {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

/*thisServer.synchronized*/ {}

override def didSave(params: DidSaveTextDocumentParams): Unit =
/*thisServer.synchronized*/ {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

/** Run the Dotty Language Server.
*
* This is designed to be started from an editor supporting the Language Server
* Protocol, the easiest way to fetch and run this is to use `coursier`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment

* This is designed to be started from an editor supporting the Language Server
* Protocol, the easiest way to fetch and run this is to use `coursier`:
*
* coursier launch $artifact -M dotty.tools.languageserver.Main -- -stdio
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

```shell?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it, and it doesn't do any highlighting on this line, so no point in using this syntax.

Copy link
Member Author

@smarter smarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

classRoot.symbol.asClass.unpickler = unpickler
moduleRoot.symbol.asClass.unpickler = unpickler
case _ =>
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by RT here? Referentially transparent? This method is never called multiple times on the same denotation.

*/
@tailrec def sourceSymbol(sym: Symbol)(implicit ctx: Context): Symbol =
if (!sym.exists)
sym
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's completely equivalent, do you want me to change this for style reasons?


private var myCtx: Context = myInitCtx

def currentCtx: Context = myCtx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, very much so, see usage in DottyLanguageServer

private val myOpenedTrees = new mutable.LinkedHashMap[URI, List[SourceTree]]

def openedFiles: Map[URI, SourceFile] = myOpenedFiles
def openedTrees: Map[URI, List[SourceTree]] = myOpenedTrees
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see usage in DottyLanguageServer


## Current limitations, to be fixed:
- Projects should be compiled with sbt before starting the IDE, this is
automatically done for you if you run `sbt launchIDE`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't start if the project doesn't compile.


private[this] var myDrivers: mutable.Map[ProjectConfig, InteractiveDriver] = _

def drivers: mutable.Map[ProjectConfig, InteractiveDriver] = thisServer.synchronized {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, it shouldn't be.


def drivers: mutable.Map[ProjectConfig, InteractiveDriver] = thisServer.synchronized {
if (myDrivers == null) {
assert(rootUri != null, "`drivers` cannot be called before `initialize`")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, initialize is the first message sent by the client to the server(https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize), if this message wasn't sent, the serve will not do anything, so this method won't be called.

val driver = driverFor(uri)

val change = params.getContentChanges.get(0)
assert(change.getRange == null, "TextDocumentSyncKind.Incremental support is not implemented")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should never trigger because in initialize we set c.setTextDocumentSync(TextDocumentSyncKind.Full), meaning we don't support TextDocumentSyncKind.Incremental. didChange is a notification, not a request, so the client does not expect a response. Note that this assertion will be caught by LSP4J and so won't bring down the entire language server.

}

override def didChangeConfiguration(params: DidChangeConfigurationParams): Unit =
/*thisServer.synchronized*/ {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A docstring for what? If you're curious what didChangeConfiguration is for, see the spec: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#didchangeconfiguration-notification
The commented synchronized is so that if we ever implement this method, we don't forget to make it synchronized

* This is designed to be started from an editor supporting the Language Server
* Protocol, the easiest way to fetch and run this is to use `coursier`:
*
* coursier launch $artifact -M dotty.tools.languageserver.Main -- -stdio
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it, and it doesn't do any highlighting on this line, so no point in using this syntax.

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for minor comments, LGTM

}))

if (currentHasPos) {
p match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we factor this out into a method forceIfLazy in Positioned?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but I think it would be even better if we used something higher-level than dealing with productIterator by hand, that would take care of lazy trees, I'll leave a FIXME in the code.

val currentHasPos =
p.pos.contains(pos) && (!p.pos.isZeroExtent ||
(path match {
case p1 :: _ => p1.pos.isZeroExtent || !p1.pos.contains(pos)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have an explanation of this logic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I forgot to clean this up, and trying to document it I realized it's broken: I was trying to get the next node in the tree that the path could go through, but this is actually pretty hard to do. I've given up on that and instead added a skipZeroExtent parameter in NavigateAST#pathTo (that I use in Interactive#pathTo): b3befee

@@ -552,6 +553,33 @@ object Symbols {

type ThisName = TypeName

/** If this is a top-level class, and if `-Ykeep-trees` is set, return the tree
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be -Yretain-trees.

@@ -552,6 +553,33 @@ object Symbols {

type ThisName = TypeName

/** If this is a top-level class, and if `-Ykeep-trees` is set, return the tree
* for this class, otherwise null.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use EmptyTree as a sentinel instead? I am not keen on having null as a semantically significant value except when used locally meaning "this cache has not been set".

*/
def tree(implicit ctx: Context): tpd.TypeDef = {
if (unpickler != null && !denot.isAbsent) {
assert(myTree == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's scope to unify the way we do this here with how we store lazy trees in inline functions. Not necessary to do this now, but it would be good to leave a comment telling us to come back to it later.

else sym.companionModule.info.member(name).symbol
else {
var module = sym.companionModule
if (module == NoSymbol && sym.isAbsent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!module.exists is more idiomatic

*/
@tailrec def sourceSymbol(sym: Symbol)(implicit ctx: Context): Symbol =
if (!sym.exists)
sym
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either works fine.

@smarter
Copy link
Member Author

smarter commented May 26, 2017

java.io.IOException: Cannot run program "code": error=2, No such file or directory

You need code (which is the name of the vscode binary) to be in your PATH. Look at where vscode was installed to find it. I'll update the doc to mention that, but unfortunately I don't know what step-by-step instructions to give to do this on every operating system.

@felixmulder
Copy link
Contributor

$ brew cask install visual-studio-code

Is the quick way on macOS

@odersky
Copy link
Contributor

odersky commented May 26, 2017

@smarter Thanks for the tip. I could launch it now. Looks pretty! I tried it on dotty itself, but so far did not get much to work beyond syntax highlighting. Let's check on Monday together?

@smarter
Copy link
Member Author

smarter commented May 26, 2017

@odersky Sure, did you get any message in the console you launched it from?

@odersky
Copy link
Contributor

odersky commented May 26, 2017

@smarter In the console everything looked normal. Here's what I got:

/Users/odersky/workspace/dotty> sbt launchIDE
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
[warn] Executing in batch mode.
[warn] For better performance, hit [ENTER] to switch to interactive mode, or
[warn] consider launching sbt without any commands, or explicitly passing 'shell'
[info] Loading project definition from /Users/odersky/workspace/dotty/project/project
[info] Loading project definition from /Users/odersky/workspace/dotty/project
[info] Resolving key references (11645 settings) ...
[info] Set current project to dotty (in build file:/Users/odersky/workspace/dotty/)
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[info] Packaging /Users/odersky/workspace/dotty/compiler/target/scala-2.11/dotty-compiler_2.11-0.1.1-bin-SNAPSHOT-nonbootstrapped.jar ...
[info] Done packaging.
[success] Total time: 4 s, completed May 26, 2017 5:17:43 PM
Extension 'lampepfl.dotty' is already installed.
[success] Total time: 0 s, completed May 26, 2017 5:17:43 PM
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
[success] Total time: 1 s, completed May 26, 2017 5:17:44 PM

  1. Waiting for source changes... (press enter to interrupt)

@smarter
Copy link
Member Author

smarter commented May 26, 2017

@odersky: Thanks to @liufengyun I fixed a few issues that could have prevented the IDE from running and updated the branch. But the sbt output you're getting makes me wonder if you're on the correct branch, can you check that you're on branch ide from staging and not ide-support or some other stale branch? Also make sure to get the latest version of the branch after my recent fixes.

smarter added 15 commits May 26, 2017 20:00
FIXME: This is too easy to forget, some sort of Traverser should be used
instead of using productIterator by hand.
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`.
When we unpickle a Scala2 module we might not have a companion class to unpickle,
when this happen we call markAbsent() on the denotation of the companion
class, which means that calls to companionModule will not work. Fixed by
falling back to the more robust scalacLinkedClass.
If a tree has no position or a zero-extent position, it should be
synthetic. We can preserve this invariant by always setting a
zero-extent position for these trees here.
Synthetic trees should get zero-extent positions so that they don't get
returned by Interactive#pathTo. This makes it easier to show information
related to non-synthetic trees in IDEs.
Previously, the position of class parents was the position of the whole
class, which means that NavigateAST#pathTo always returned the class
parents when looking up a position in the body of the class. Fixed by
using the position of the name of the class instead.
The position of a tree should contain the position of all its children,
but that was not the case before this commit for Bind nodes created from
Typed nodes.
Before this commit, we only created .tasty files under -YemitTasty, we
haven't decided yet if this should be the default, but even if we don't
end up doing that, it is still useful to emit empty .tasty files to
signal that the corresponding .class file has a tasty section, this is
much simpler to check than parsing classfiles to look for a tasty section.

Also, fix -YemitTasty tof work with AbstractFile who are not backed by
java.io.File, previously this lead to NullPointerException.
As specified in https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.16.1,
an annotation argument of type boolean, byte, char or short will be represented
as a CONSTANT_Integer in the classfile, so when we parse it we get an
Int, but before this commit we did not convert this Int to the correct
type for the annotation argument, thus creating ill-typed trees.
This way they won't show up in the IDE
smarter added 6 commits May 26, 2017 20:00
This commit adds a set of APIs in dotty.tools.dotc.interactive._
designed for interactive uses of the compiler like in IDEs. This API is
just a first draft driven by the requirements of the Dotty Language
Server and will evolve based on usage.

Usage is roughly as follow:
- Create an instance of InteractiveDriver with the compiler flags you need
- Call InteractiveDriver#run(uri, code) to typecheck `code` and keep a
  reference to it via the identifier `uri`. The return value of this
  method are the errors/warnings/infos generated during the compilation
- Use InteractiveDriver#openedTrees(uri) to get all top-level class
  typechecked trees generated for `uri`, or InteractiveDriver#allTrees
  to get all top-level trees opened in this driver instance and
  available on the classpath (unpickled from the TASTY section of classfiles)
- Query the trees using one of the many methods in the Interactive object.

See the code in language-server/ added in the following
commit for a concrete example.
This is an implementation of the Language Server Protocol for Dotty,
which allows us to provide rich IDE features in every editor supporting
this protocol, a Visual Studio Code extension demonstrating this is part
of the next commit. For more information on the LSP, see
https://github.com/Microsoft/language-server-protocol

Fully supported features:
- Typechecking as you type to show compiler errors/warnings
- Type information on hover
- Go to definition (in the current project)
- Find all references

Partially working features:
- Completion
- Renaming
- Go to definition in external projects

Unimplemented features:
- Documentation on hover
- Formatting code (requires integrating with scalafmt)
- Quick fixes (probably by integrating with scalafix)

Current limitations, to be fixed:
- Projects should be compiled with sbt before starting the IDE, this is
  automatically done for you if you run `sbt launchIDE`.
- Once the IDE is started, source files that are not opened in the IDE
  should not be modified in some other editor, the IDE won't pick up
  these changes.
- Not all compiler errors/warnings are displayed, just those occuring
  during typechecking.
This extension uses the Dotty Language Server to provide IDE features
in Visual Studio Code.
This means the dotty build can use the latest unreleased version of the
sbt-dotty plugin instead of depending on a published version, in the
next commit we'll add IDE support to the sbt-dotty plugin and immediately
be able to use them on the dotty build itself.
This adds commands to the sbt-dotty plugin to generate the
.dotty-ide.json config file used by the Dotty Language Server and to
start Visual Studio Code with everything set up correctly. For
end-users, using the IDE is as simple as:
1. Installing vscode (https://code.visualstudio.com)
2. In your project, run `sbt launchIDE`
@HerringtonDarkholme
Copy link

I encounter a NPE when firing up sbt launcheIDE in https://github.com/lampepfl/dotty-example-project.

Seems https://github.com/lampepfl/dotty/blob/352c09e481cb4893812c5f49959e587842448fe0/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala#L83 drivers.keys.head throws NPE because drivers is empty. Still investigating what happened here. I will report further info in another issue if I find out why.

@smarter
Copy link
Member Author

smarter commented Jun 1, 2017

@HerringtonDarkholme could you post the content of the generated .dotty-ide.json file in the project, as well as the output of the pwd command in a shell in the project directory? What operating system are you on?

@smarter
Copy link
Member Author

smarter commented Jun 1, 2017

The exact stacktrace you got would be helpful too

@HerringtonDarkholme
Copy link

I'm on Mac OSX.

.dotty-ide.json

[ {
  "id" : "root/compile",
  "compilerVersion" : "0.1.2-RC1",
  "compilerArguments" : [ ],
  "sourceDirectories" : [ "/test/dotty-example-project/src/main/scala-0.1", "/test/dotty-example-project/src/main/scala", "/test/dotty-example-project/src/main/java", "/test/dotty-example-project/target/scala-0.1/src_managed/main" ],
  "dependencyClasspath" : [ "/.ivy2/cache/ch.epfl.lamp/scala-library/jars/scala-library-0.1.2-RC1.jar", "/.ivy2/cache/ch.epfl.lamp/dotty-library_0.1/jars/dotty-library_0.1-0.1.2-RC1.jar", "/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.11.jar" ],
  "classDirectory" : "/test/dotty-example-project/target/scala-0.1/classes"
} ]

CallStack:

Starting server
Jun 02, 2017 9:18:41 AM org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint notify
INFO: Unsupported notification method: $/setTraceNotification
Jun 02, 2017 9:18:42 AM org.eclipse.lsp4j.jsonrpc.RemoteEndpoint handleNotification

java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
	at org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.lambda$null$0(GenericEndpoint.java:53)
	at org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.notify(GenericEndpoint.java:126)
	at org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.handleNotification(RemoteEndpoint.java:165)
	at org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.consume(RemoteEndpoint.java:136)
	at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.handleMessage(StreamMessageProducer.java:149)
	at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.listen(StreamMessageProducer.java:77)
	at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor.run(ConcurrentMessageProcessor.java:84)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.lambda$null$0(GenericEndpoint.java:51)
	... 11 more
Caused by: java.lang.NullPointerException
	at scala.collection.mutable.HashTable$$anon$1.next(HashTable.scala:219)
	at scala.collection.mutable.HashTable$$anon$1.next(HashTable.scala:211)
	at scala.collection.mutable.HashMap$$anon$3.next(HashMap.scala:146)
	at scala.collection.IterableLike$class.head(IterableLike.scala:107)
	at scala.collection.AbstractIterable.head(Iterable.scala:54)
	at dotty.tools.languageserver.DottyLanguageServer.driverFor(DottyLanguageServer.scala:83)
	at dotty.tools.languageserver.DottyLanguageServer.didOpen(DottyLanguageServer.scala:143)
	... 16 more

java.lang.NullPointerException
	at scala.collection.mutable.HashTable$$anon$1.next(HashTable.scala:219)
	at scala.collection.mutable.HashTable$$anon$1.next(HashTable.scala:211)
	at scala.collection.mutable.HashMap$$anon$3.next(HashMap.scala:146)
	at scala.collection.IterableLike$class.head(IterableLike.scala:107)
	at scala.collection.AbstractIterable.head(Iterable.scala:54)
	at dotty.tools.languageserver.DottyLanguageServer.driverFor(DottyLanguageServer.scala:83)
	at dotty.tools.languageserver.DottyLanguageServer.hover$$anonfun$1(DottyLanguageServer.scala:286)
	at dotty.tools.languageserver.DottyLanguageServer.liftedTree1$1(DottyLanguageServer.scala:107)
	at dotty.tools.languageserver.DottyLanguageServer.computeAsync$$anonfun$1(DottyLanguageServer.scala:112)
	at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
	at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
	at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:443)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Jun 02, 2017 9:18:42 AM org.eclipse.lsp4j.jsonrpc.RemoteEndpoint lambda$static$0
SEVERE: Internal error: java.lang.NullPointerException
java.util.concurrent.CompletionException: java.lang.NullPointerException
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:604)
	at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
	at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:443)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.NullPointerException
	at scala.collection.mutable.HashTable$$anon$1.next(HashTable.scala:219)
	at scala.collection.mutable.HashTable$$anon$1.next(HashTable.scala:211)
	at scala.collection.mutable.HashMap$$anon$3.next(HashMap.scala:146)
	at scala.collection.IterableLike$class.head(IterableLike.scala:107)
	at scala.collection.AbstractIterable.head(Iterable.scala:54)
	at dotty.tools.languageserver.DottyLanguageServer.driverFor(DottyLanguageServer.scala:83)
	at dotty.tools.languageserver.DottyLanguageServer.hover$$anonfun$1(DottyLanguageServer.scala:286)
	at dotty.tools.languageserver.DottyLanguageServer.liftedTree1$1(DottyLanguageServer.scala:107)
	at dotty.tools.languageserver.DottyLanguageServer.computeAsync$$anonfun$1(DottyLanguageServer.scala:112)
	at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
	... 6 more

[Error - 9:18:42 AM] Request textDocument/hover failed.
  Message: Internal error, please look at the server's logs.
  Code: -32603 

@smarter
Copy link
Member Author

smarter commented Jun 2, 2017

@HerringtonDarkholme My guess is that you have some scala files directly in the directory /test/dotty-example-project. If so, try moving them inside /test/dotty-example-project/src/main/scala (and I'll make sure to fix the IDE support so that this is not needed soon). Otherwise, could you post the output of ls -R /test/dotty-example-project, and the path of the file you tried to open ?

@HerringtonDarkholme
Copy link

This is my tree -L 2 output.

.
├── LICENSE
├── README.md
├── build.sbt
├── project
│   ├── build.properties
│   ├── plugins.sbt
│   ├── project
│   └── target
├── src
│   └── main
└── target
    ├── resolution-cache
    ├── scala-0.1
    └── streams

@wxdong5211
Copy link

wxdong5211 commented Jun 2, 2017

dottynpes
me too on windoge 8

@HerringtonDarkholme
Copy link

I have identified one problem. A for comprehension will catch all Exception. https://github.com/lampepfl/dotty/blob/master/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala#L65-L69

And alas, InteractiveDriver will initialize Context and thus ScalaSettings where sys.props("user.dir") will be queried.

However, if user has already opened vscode, launchIDE will reuse vscode instance instead of creating new instance in working directory. I personally open vscode from dock where user.dir is a /. Thus https://github.com/lampepfl/dotty/blob/352c09e481cb4893812c5f49959e587842448fe0/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala#L124 throws and swallowed by for.

@smarter
Copy link
Member Author

smarter commented Jun 2, 2017

@HerringtonDarkholme Thanks for the diagnostic! Can you try forcing code to run in a new instance using code -n /test/dotty-example-project and see if that helps? I'll make a PR to fix the exception when user.dir is /.

@ouchxp
Copy link

ouchxp commented Jun 8, 2017

@smarter code -n /test/dotty-example-project works for me.

@HerringtonDarkholme
Copy link

HerringtonDarkholme commented Jun 8, 2017

Yes, fresh launching makes everything work. ( forget to report back, sorry

@smarter
Copy link
Member Author

smarter commented Jun 21, 2017

@wxdong5211 @HerringtonDarkholme Your issues should now be fixed if you use the latest nightly build of dotty (scalaVersion := "0.2.0-bin-20170620-1d05796-NIGHTLY" in your build) and the latest version of sbt-dotty (addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.1.3") in project/plugins.sbt). Let me know if something is still broken!

@HerringtonDarkholme
Copy link

It works fine for me! Thank @smarter !

@wxdong5211
Copy link

windoge ok too , @smarter 👍

@allanrenucci allanrenucci deleted the ide branch December 14, 2017 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants