diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index 14cdce0e069..a418039138f 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -112,6 +112,8 @@ class Compilers( ) private val worksheetsDigests = new TrieMap[AbsolutePath, String]() + private val wasSuccessfullyCompiled = + new TrieMap[BuildTargetIdentifier, Boolean]() private val cache = jcache.asScala private def buildTargetPCFromCache( @@ -272,6 +274,11 @@ class Compilers( val isSuccessful = report.getErrors == 0 buildTargetPCFromCache(report.getTarget).foreach(_.restart(isSuccessful)) + wasSuccessfullyCompiled.updateWith(report.getTarget()) { + case Some(true) => Some(true) + case _ => Some(isSuccessful) + } + if (isSuccessful) { // Restart PC for all build targets that depend on this target since the classfiles // may have changed. @@ -1188,7 +1195,7 @@ class Compilers( classpath: Seq[Path], search: SymbolSearch, ): PresentationCompiler = { - newCompiler( + val pc = newCompiler( mtags, target.scalac.getOptions().asScala.toSeq, classpath, @@ -1196,6 +1203,11 @@ class Compilers( target.scalac.getTarget.getUri, ).withBuildTargetName(target.displayName) .withCompilerFiles(new TargetCompilerFiles(target.id)) + + wasSuccessfullyCompiled + .get(target.scalac.getTarget()) + .map(pc.withWasSuccessfullyCompiled) + .getOrElse(pc) } def newCompiler( diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java index 800d3d2f7dd..ad3cdf3209d 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -206,7 +206,7 @@ public CompletableFuture> syntheticDecorations(Synthet /** * Clean the symbol table and other mutable state in the compiler. */ - public void restart(Boolean successfulCompilation) { + public void restart(boolean successfulCompilation) { restart(); } @@ -324,4 +324,8 @@ public abstract PresentationCompiler newInstance(String buildTargetIdentifier, L */ public abstract String scalaVersion(); + public PresentationCompiler withWasSuccessfullyCompiled(boolean wasSuccessful) { + return this; + } + } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/CompilerWrapper.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/CompilerWrapper.scala index a6932b9d02e..8836721b802 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/CompilerWrapper.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/CompilerWrapper.scala @@ -28,5 +28,5 @@ case class OutlineFiles( ) object OutlineFiles { - def empty = OutlineFiles(Nil) + def empty: OutlineFiles = OutlineFiles(Nil) } diff --git a/mtags/src/main/scala-2.11/scala/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.11/scala/meta/internal/pc/Compat.scala index 3d2936e86f8..8b77efec49d 100644 --- a/mtags/src/main/scala-2.11/scala/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.11/scala/meta/internal/pc/Compat.scala @@ -2,7 +2,6 @@ package scala.meta.internal.pc import scala.tools.nsc.reporters.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.pc.VirtualFileParams trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = { @@ -21,7 +20,7 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = c - def runOutline(files: List[VirtualFileParams]): Unit = { + def runOutline(files: OutlineFiles): Unit = { // no outline compilation for 2.11 } } diff --git a/mtags/src/main/scala-2.12/scala/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.12/scala/meta/internal/pc/Compat.scala index 772066b055f..cc1c8c51a6f 100644 --- a/mtags/src/main/scala-2.12/scala/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.12/scala/meta/internal/pc/Compat.scala @@ -2,6 +2,7 @@ package scala.meta.internal.pc import scala.tools.nsc.reporters.Reporter import scala.tools.nsc.reporters.StoreReporter + import scala.meta.pc.VirtualFileParams trait Compat { this: MetalsGlobal => @@ -18,18 +19,32 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = c - def runOutline(files: List[VirtualFileParams]): Unit = { + def runOutline(files: OutlineFiles): Unit = { this.settings.Youtline.value = true + runOutline(files.files) + if (files.firstCompileSubstitute) { + // if first compilation substitute we compile all files twice + // first to emit symbols, second so signatures have information about those symbols + // this isn't a perfect strategy but much better than single compile + runOutline(files.files, forceNewUnit = true) + } + this.settings.Youtline.value = false + } + + private def runOutline( + files: List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { files.foreach { params => val unit = this.addCompilationUnit( params.text(), params.uri.toString(), cursor = None, - isOutline = true + isOutline = true, + forceNew = forceNewUnit ) this.typeCheck(unit) this.richCompilationCache.put(params.uri().toString(), unit) } - this.settings.Youtline.value = false } } diff --git a/mtags/src/main/scala-2.13/scala/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13/scala/meta/internal/pc/Compat.scala index eea56572e90..19e8ec15b9c 100644 --- a/mtags/src/main/scala-2.13/scala/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13/scala/meta/internal/pc/Compat.scala @@ -27,27 +27,31 @@ trait Compat { this: MetalsGlobal => if (c.value.isSuitableLiteralType) LiteralType(c.value) else c def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true runOutline(files.files) - if(files.firstCompileSubstitute) { + if (files.firstCompileSubstitute) { // if first compilation substitute we compile all files twice // first to emit symbols, second so signatures have information about those symbols // this isn't a perfect strategy but much better than single compile runOutline(files.files, forceNewUnit = true) } + this.settings.Youtline.value = false } - private def runOutline(files: List[VirtualFileParams], forceNewUnit: Boolean = false): Unit = { - this.settings.Youtline.value = true + private def runOutline( + files: List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { files.foreach { params => val unit = this.addCompilationUnit( params.text(), params.uri.toString(), cursor = None, - isOutline = true + isOutline = true, + forceNew = forceNewUnit ) this.typeCheck(unit) this.richCompilationCache.put(params.uri().toString(), unit) } - this.settings.Youtline.value = false } } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala index c7eaef5319c..4bc0c4cfef5 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala @@ -66,7 +66,8 @@ class MetalsGlobal( val logger: Logger = Logger.getLogger(classOf[MetalsGlobal].getName) - val richCompilationCache: TrieMap[String,RichCompilationUnit] = TrieMap.empty[String, RichCompilationUnit] + val richCompilationCache: TrieMap[String, RichCompilationUnit] = + TrieMap.empty[String, RichCompilationUnit] // for those paths units were val fullyCompiled: mutable.Set[String] = mutable.Set.empty[String] @@ -652,7 +653,8 @@ class MetalsGlobal( filename: String, cursor: Option[Int], cursorName: String = CURSOR, - isOutline: Boolean = false + isOutline: Boolean = false, + forceNew: Boolean = false ): RichCompilationUnit = { val codeWithCursor = cursor match { case Some(offset) => @@ -671,11 +673,11 @@ class MetalsGlobal( if util.Arrays.equals( value.source.content, richUnit.source.content - ) && (isOutline || fullyCompiled(filename)) => + ) && (isOutline || fullyCompiled(filename)) && !forceNew => value case _ => unitOfFile(richUnit.source.file) = richUnit - if(!isOutline) { + if (!isOutline) { fullyCompiled += filename } else { fullyCompiled -= filename diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaCompilerAccess.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaCompilerAccess.scala index af9681f0054..3ad5ab0d4b7 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaCompilerAccess.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaCompilerAccess.scala @@ -9,7 +9,6 @@ import scala.util.control.NonFatal import scala.meta.internal.metals.ReportContext import scala.meta.pc.PresentationCompilerConfig -import scala.meta.pc.VirtualFileParams class ScalaCompilerWrapper(global: MetalsGlobal) extends CompilerWrapper[StoreReporter, MetalsGlobal] { diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 5ac337be247..69556f1a128 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -67,10 +67,14 @@ case class ScalaPresentationCompiler( config: PresentationCompilerConfig = PresentationCompilerConfigImpl(), folderPath: Option[Path] = None, reportsLevel: ReportLevel = ReportLevel.Info, - compilerFiles: Option[CompilerFiles] = None + compilerFiles: Option[CompilerFiles] = None, + wasSuccessfullyCompiledInitial: Option[Boolean] = None ) extends PresentationCompiler { - private val wasFirstCompilationSuccessful = new util.concurrent.atomic.AtomicReference[Option[Boolean]](None) + private val wasSuccessfullyCompiled = + new util.concurrent.atomic.AtomicReference[Option[Boolean]]( + wasSuccessfullyCompiledInitial + ) implicit val executionContext: ExecutionContextExecutor = ec @@ -147,13 +151,14 @@ case class ScalaPresentationCompiler( def restart(): Unit = restoreOutlineAndRestart() - override def restart(wasSuccessful: java.lang.Boolean): Unit = { - wasFirstCompilationSuccessful.updateAndGet { + override def restart(wasSuccessful: Boolean): Unit = { + wasSuccessfullyCompiled.updateAndGet { case None => Some(wasSuccessful) + case _ if wasSuccessful => Some(true) case value => value } - if(wasSuccessful) { + if (wasSuccessful) { changedDocuments.clear() compilerAccess.shutdownCurrentCompiler() } else { @@ -168,26 +173,25 @@ case class ScalaPresentationCompiler( EmptyCancelToken ) { pc => /* we will still want outline recompiled if the compilation was not succesful */ - pc.compiler().richCompilationCache.foreach { - case (uriString, unit) => - try { - val text = unit.source.content.mkString - val uri = uriString.toAbsolutePath.toURI - val params = - CompilerOffsetParams(uri, text, 0, EmptyCancelToken) - changedDocuments += uri -> params - } catch { - case NonFatal(error) => - reportContex.incognito.create( - Report( - "restoring_cache", - "Error while restoring outline compiler cache", - error - ) + pc.compiler().richCompilationCache.foreach { case (uriString, unit) => + try { + val text = unit.source.content.mkString + val uri = uriString.toAbsolutePath.toURI + val params = + CompilerOffsetParams(uri, text, 0, EmptyCancelToken) + changedDocuments += uri -> params + } catch { + case NonFatal(error) => + reportContex.incognito.create( + Report( + "restoring_cache", + "Error while restoring outline compiler cache", + error ) - logger - .log(util.logging.Level.SEVERE, error.getMessage(), error) - } + ) + logger + .log(util.logging.Level.SEVERE, error.getMessage(), error) + } } } .get() @@ -212,8 +216,15 @@ case class ScalaPresentationCompiler( private def outlineFiles( current: VirtualFileParams ): OutlineFiles = { + val shouldCompileAll = wasSuccessfullyCompiled + .getAndUpdate { + case Some(false) => Some(true) + case value => value + } + .exists(!_) + val result = - if (wasFirstCompilationSuccessful.getAndSet(Some(true)).exists(!_)) { + if (shouldCompileAll) { // if first compilation was unsuccessful we want to outline all files compilerFiles.iterator.flatMap(_.allPaths().asScala).foreach { path => val uri = path.toUri() @@ -222,9 +233,13 @@ case class ScalaPresentationCompiler( changedDocuments += uri -> CompilerOffsetParams(uri, text, 0) } } - OutlineFiles(changedDocuments.values.toList, firstCompileSubstitute = true) + OutlineFiles( + changedDocuments.values.toList, + firstCompileSubstitute = true + ) } else { - val files = changedDocuments.values.filterNot(_.uri() == current.uri()).toList + val files = + changedDocuments.values.filterNot(_.uri() == current.uri()).toList OutlineFiles(files) } @@ -282,7 +297,7 @@ case class ScalaPresentationCompiler( override def complete( params: OffsetParams - ): CompletableFuture[CompletionList] = + ): CompletableFuture[CompletionList] = { compilerAccess.withInterruptableCompiler(Some(params))( EmptyCompletionList(), params.token @@ -290,6 +305,7 @@ case class ScalaPresentationCompiler( new CompletionProvider(pc.compiler(outlineFiles(params)), params) .completions() } + } override def implementAbstractMembers( params: OffsetParams @@ -579,4 +595,11 @@ case class ScalaPresentationCompiler( .toList .asJava } + + override def withWasSuccessfullyCompiled( + wasSuccessful: Boolean + ): PresentationCompiler = { + this.copy(wasSuccessfullyCompiledInitial = Some(wasSuccessful)) + } + } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala index 124db2b71f6..66ff4565d74 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/WorkspaceSymbolSearch.scala @@ -26,7 +26,7 @@ trait WorkspaceSymbolSearch { compiler: MetalsGlobal => def matches = if (sym.isType) CompletionFuzzy.matchesSubCharacters(query, sym.name.toString()) else CompletionFuzzy.matches(query, sym.name.toString()) - if (sym.exists && matches) { + if (sym != null && sym.exists && matches) { try { visitMember(sym) } catch { diff --git a/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala b/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala index 6ec2131e181..4a67f1b84d1 100644 --- a/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala +++ b/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala @@ -266,13 +266,13 @@ class SemanticTokensLspSuite extends BaseLspSuite("SemanticTokens") { for { _ <- initialize( s"""/metals.json - |{"a":{}} - |/a/src/main/scala/a/Main.scala - | - |/a/src/main/scala/a/OtherFile.scala - |package a - |object A - |""".stripMargin, + |{"a":{}} + |/a/src/main/scala/a/Main.scala + | + |/a/src/main/scala/a/OtherFile.scala + |package a + |object A + |""".stripMargin, expectError = true, ) _ <- server.didChangeConfiguration(