Skip to content

Commit c8d27f4

Browse files
committed
feat: Don't run InteractiveDriver.run if the content didn't change
If the content didn't change since the last `driver.run`, do not compile because the compilation unit should have the up to date typed tree. Skip compilation save up much of CPU usage when users are reading source code (and hover on symbols) without modifying the source code. What happens if we change in a file affect another file's type information? For example, when we have the following source files, if we hover on `B.foo` in `A.scala`, Metals shows the symbol's type is `Int`. ```scala // A.scala def main = println(B.foo) // B.scala object B: def foo: Int = ??? ``` When we modify `B.scala` to `def foo: String = ???`, what happens to `A.scala`? You may think that, Metals keep using the old compilation unit that shows `B.foo` as `Int` instead of `String` because Metals invalidates the compilation cache based on its content hash. That's not true, `alreadyCompiled("A.scala", <content hash>)` will be `false`, and Metals will run `driver.run` on `A.scala` and show `String` for `B.foo` in `A.scala`. Why? Because Metals will recreate a new `MetalsDriver` and `lastCompiled` cache will be cleared up. - When a file has changed, Metals call [Compilers.restart](https://github.com/scalameta/metals/blob/c6e0f30febeff67e970e463d79862e27e45ada3f/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala#L247). - The restart method calls `shutdownCurrentCompiler` that [set `_compiler` to `null`.](https://github.com/scalameta/metals/blob/c6e0f30febeff67e970e463d79862e27e45ada3f/mtags/src/main/scala/scala/meta/internal/pc/CompilerAccess.scala#L57-L74). This is basically the same way to cache compilation as Scala2 See my explanation on how Scala2 + Metals compilation cache works https://contributors.scala-lang.org/t/how-does-scala2-compiler-cache-invalidate-the-typechecking-result-for-the-given-tree/5858/4?u=tanishiking
1 parent fa27adb commit c8d27f4

File tree

4 files changed

+42
-7
lines changed

4 files changed

+42
-7
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package scala.meta.internal.pc
2+
3+
import java.net.URI
4+
import java.{util as ju}
5+
6+
import scala.collection.concurrent.TrieMap
7+
8+
import scala.meta.internal.mtags.MD5
9+
10+
import dotty.tools.dotc.core.Contexts.Context
11+
import dotty.tools.dotc.interactive.InteractiveDriver
12+
import dotty.tools.dotc.interactive.SourceTree
13+
import dotty.tools.dotc.reporting.Diagnostic
14+
import dotty.tools.dotc.util.SourceFile
15+
16+
class MetalsDriver(
17+
override val settings: List[String]
18+
) extends InteractiveDriver(settings):
19+
20+
private def alreadyCompiled(uri: URI, content: Array[Char]): Boolean =
21+
compilationUnits.get(uri) match
22+
case Some(unit) if ju.Arrays.equals(unit.source.content(), content) =>
23+
true
24+
case _ => false
25+
26+
override def run(uri: URI, source: SourceFile): List[Diagnostic] =
27+
if alreadyCompiled(uri, source.content) then Nil
28+
else super.run(uri, source)
29+
30+
override def run(uri: URI, sourceCode: String): List[Diagnostic] =
31+
val contentHash = MD5.compute(sourceCode)
32+
if alreadyCompiled(uri, sourceCode.toCharArray()) then Nil
33+
else super.run(uri, sourceCode)
34+
35+
end MetalsDriver

mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerAccess.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Scala3CompilerAccess(
2424
sh: Option[ScheduledExecutorService],
2525
newCompiler: () => Scala3CompilerWrapper,
2626
)(using ec: ExecutionContextExecutor)
27-
extends CompilerAccess[StoreReporter, InteractiveDriver](
27+
extends CompilerAccess[StoreReporter, MetalsDriver](
2828
config,
2929
sh,
3030
newCompiler,

mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import dotty.tools.dotc.core.Contexts.Context
77
import dotty.tools.dotc.interactive.InteractiveDriver
88
import dotty.tools.dotc.reporting.StoreReporter
99

10-
class Scala3CompilerWrapper(driver: InteractiveDriver)
11-
extends CompilerWrapper[StoreReporter, InteractiveDriver]:
10+
class Scala3CompilerWrapper(driver: MetalsDriver)
11+
extends CompilerWrapper[StoreReporter, MetalsDriver]:
1212

13-
override def compiler(): InteractiveDriver = driver
13+
override def compiler(): MetalsDriver = driver
1414

1515
override def resetReporter(): Unit =
1616
val ctx = driver.currentCtx

mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ case class ScalaPresentationCompiler(
5454
private val forbiddenOptions = Set("-print-lines", "-print-tasty")
5555
private val forbiddenDoubleOptions = Set("-release")
5656

57-
val compilerAccess: CompilerAccess[StoreReporter, InteractiveDriver] =
57+
val compilerAccess: CompilerAccess[StoreReporter, MetalsDriver] =
5858
Scala3CompilerAccess(
5959
config,
6060
sh,
@@ -70,7 +70,7 @@ case class ScalaPresentationCompiler(
7070
case head :: tail => head :: removeDoubleOptions(tail)
7171
case Nil => options
7272

73-
def newDriver: InteractiveDriver =
73+
def newDriver: MetalsDriver =
7474
val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0")
7575
val defaultFlags = List("-color:never")
7676
val filteredOptions = removeDoubleOptions(
@@ -81,7 +81,7 @@ case class ScalaPresentationCompiler(
8181
.mkString(
8282
File.pathSeparator
8383
) :: Nil
84-
new InteractiveDriver(settings)
84+
new MetalsDriver(settings)
8585

8686
override def getTasty(
8787
targetUri: URI,

0 commit comments

Comments
 (0)