Description
Compiler version
All Scala 3 and Scala 2 versions.
Background information
IntelliJ IDEA is very particular when it comes to saving files to disk. IDEA likes to work with in-memory files and occasionally, in a very controlled manner, persist the files to disk.
In order to avoid more frequent than absolutely necessary saving to disk, I recently implemented compilation of in-memory files in IntelliJ IDEA, as part of this ticket, when using the Scala 3 compiler to provide error highlighting. This has been rolled out in an EAP version of IDEA 2024.2. If anyone wants to reproduce this project, it is enough to run IDEA 2024.2 EAP with Scala Plugin 2024.2.6 (or 2024.2.405+ nightly), clone https://github.com/scala/scala3-example-project and open the MySuite.scala
MUnit test suite.
Initially, everything worked fine. But I didn't think to test it with sources that contain macro expansion.
Since then, there has been the following report.
When in-memory source files are used, the following MUnit macro implementation throws an NPE.
For easy reference, here's the exception:
Exception occurred while executing macro expansion. java.lang.NullPointerException: Cannot invoke "java.nio.file.Path.toString()" because the return value of "scala.quoted.Quotes$reflectModule$SourceFileMethods.jpath(Object)" is null at munit.internal.MacroCompat$.locationImpl(MacroCompat. scala:18)
This specific MUnit Location macro is a perfectly well-written macro. In fact, the Scala 3 documentation shows the same idiom.
What I think the problem is
I searched for "def jpath"
in the Scala 3 repo and found the following:
all of which are implemented as:
/** Returns null. */
def jpath: JPath = null
To be fair, these are completely legitimate implementations of the dotty.tools.io.AbstractFile
interface, which says:
/** Returns the underlying Path if any and null otherwise. */
def jpath: JPath
(in both cases, JPath
is a type alias for java.nio.file.Path
)
What I tried
I modified VirtualFile.scala
in the Scala 3 repo to return:
class VirtualFile(val name: String, override val path: String) extends AbstractFile {
...
def jpath: JPath = Paths.get(path)
...
}
I published a local snapshot and rebased https://github.com/scala/scala3-example-project on it, and the issue was gone.
What will we do
Obviously, I will have to disable in-memory sources and revert to always using file system sources for providing error highlighting using the compiler. I will keep the feature around with the hope of enabling it for future versions of Scala.
I will also open the same ticket in Scala 2, as it also contains the same null
implementations. That ticket will most likely be just a reference to this one, as the relevant details are the same.
Discussion
How should this problem be handled? When a proper solution for this problem is attempted, it should also be covered by tests, not just for the regular compilation part, but also macro expansion, and any other places that might be using AbstractFile#jpath
.
It also feels like the macro documentation should be revised, as it clearly shows unsafe usage of methods that are documented to be nullable.
Looking forward to a discussion.
Thank you in advance.