From 2c1ad9ff85c79eb6809b04ae82c45ab7ad2fc1b5 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Mon, 23 Oct 2023 08:59:44 +0200 Subject: [PATCH 01/20] feature: Experiment with outline compielr --- .../meta/internal/metals/Compilers.scala | 22 ++- .../internal/metals/MetalsLspService.scala | 9 +- .../java/scala/meta/pc/CompilerFiles.java | 6 + .../scala/meta/pc/PresentationCompiler.java | 16 +- .../pc/JavaPresentationCompiler.scala | 1 + .../meta/internal/metals/Compression.scala | 2 +- .../meta/internal/pc/CompilerWrapper.scala | 5 +- .../internal/pc/AutoImportsProvider.scala | 6 +- .../scala/meta/internal/pc/MetalsGlobal.scala | 77 ++++++-- .../internal/pc/ScalaCompilerAccess.scala | 6 +- .../pc/ScalaPresentationCompiler.scala | 149 ++++++++++++--- .../internal/pc/SymbolSearchCandidate.scala | 4 +- .../internal/pc/WorkspaceSymbolSearch.scala | 39 +++- .../internal/pc/Scala3CompilerWrapper.scala | 5 +- .../test/scala/tests/CompilersLspSuite.scala | 173 ++++++++++++++++++ 15 files changed, 466 insertions(+), 54 deletions(-) create mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java 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 b226eaec882..f65dfd02359 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -27,6 +27,7 @@ import scala.meta.internal.worksheets.WorksheetProvider import scala.meta.io.AbsolutePath import scala.meta.pc.AutoImportsResult import scala.meta.pc.CancelToken +import scala.meta.pc.CompilerFiles import scala.meta.pc.HoverSignature import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompiler @@ -101,6 +102,21 @@ class Compilers( import compilerConfiguration._ + // TODO + // val wasPreviouslyCompiledSuccesfully = new ConcurrentHashSet[BuildTargetIdentifier] + + class TargetCompilerFiles(targetId: BuildTargetIdentifier) + extends CompilerFiles { + override def allPaths(): ju.List[Path] = { + buildTargets + .buildTargetSources(targetId) + .flatMap(_.listRecursive.toList) + .map(_.toNIO) + .toList + .asJava + } + } + // Not a TrieMap because we want to avoid loading duplicate compilers for the same build target. // Not a `j.u.c.ConcurrentHashMap` because it can deadlock in `computeIfAbsent` when the absent // function is expensive, which is the case here. @@ -242,15 +258,17 @@ class Compilers( def didCompile(report: CompileReport): Unit = { if (report.getErrors > 0) { - buildTargetPCFromCache(report.getTarget).foreach(_.restart()) + buildTargetPCFromCache(report.getTarget).foreach(_.restart(false)) } else { // Restart PC for all build targets that depend on this target since the classfiles // may have changed. + + // TODO should mark as succesful for { target <- buildTargets.allInverseDependencies(report.getTarget) compiler <- buildTargetPCFromCache(target) } { - compiler.restart() + compiler.restart(true) } } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 126a8e3dcf3..dd260530436 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -866,8 +866,13 @@ abstract class MetalsLspService( val path = params.getTextDocument.getUri.toAbsolutePath buffers.put(path, change.getText) diagnostics.didChange(path) - - parseTrees(path).asJava + compilers.didChange(path) + parseTrees(path) + .map { _ => + treeView.onWorkspaceFileDidChange(path) + } + .ignoreValue + .asJava } } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java b/mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java new file mode 100644 index 00000000000..68adb2acfed --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java @@ -0,0 +1,6 @@ +package scala.meta.pc; + +public interface CompilerFiles { + + java.util.List allPaths(); +} 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 095f681b537..c8e6ba6f458 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -220,7 +220,14 @@ public CompletableFuture> info(String symbol) { /** * Clean the symbol table and other mutable state in the compiler. */ - public abstract void restart(); + public abstract void restart(Boolean successfulCompilation); + + /** + * Clean the symbol table and other mutable state in the compiler. + */ + public void restart(){ + restart(true); + } /** * Set logger level for reports. @@ -236,6 +243,13 @@ public PresentationCompiler withBuildTargetName(String buildTargetName) { return this; }; + /** + * Set an ability to search for all files within the compiler + */ + public PresentationCompiler withCompilerFiles(CompilerFiles files) { + return this; + }; + /** * Provide a SymbolSearch to extract docstrings, java parameter names and Scala * default parameter values. diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala index 17239c2a7cc..a9d21f7a202 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala @@ -44,6 +44,7 @@ case class JavaPresentationCompiler( workspace: Option[Path] = None ) extends PresentationCompiler { + override def restart(x$1: lang.Boolean): Unit = () private val javaCompiler = new JavaMetalsGlobal(search, config, classpath) override def complete( diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/Compression.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/Compression.scala index cc48fe816d4..136f2d3f9c9 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/Compression.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/Compression.scala @@ -55,7 +55,7 @@ object Compression { pkg = in.readString() case 0 => isDone = true - case _ => + case tag => in.skipField(tag) } } 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 20dbeb0631f..a8fb6bd730e 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 @@ -1,5 +1,7 @@ package scala.meta.internal.pc +import scala.meta.pc.VirtualFileParams + trait CompilerWrapper[Reporter, Compiler] { def resetReporter(): Unit @@ -12,7 +14,8 @@ trait CompilerWrapper[Reporter, Compiler] { def stop(): Unit - def compiler(): Compiler + def compiler(changeFiles: List[VirtualFileParams]): Compiler def presentationCompilerThread: Option[Thread] + } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala index d891c06f88b..0dcb161221b 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala @@ -30,7 +30,7 @@ final class AutoImportsProvider( val isSeen = mutable.Set.empty[String] val symbols = List.newBuilder[Symbol] - def visit(sym: Symbol): Boolean = { + def visit(fromWorkspace: Boolean)(sym: Symbol): Boolean = { val id = sym.fullName if (!isSeen(id)) { isSeen += id @@ -40,8 +40,10 @@ final class AutoImportsProvider( false } - val visitor = new CompilerSearchVisitor(context, visit) + compiler.searchOutline(visit(fromWorkspace = false), name) + val visitor = + new CompilerSearchVisitor(context, visit(fromWorkspace = true)) search.search(name, buildTargetIdentifier, visitor) def isInImportTree: Boolean = lastVisitedParentTrees match { 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 8171bab24c3..a08013aee4a 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 @@ -30,6 +30,7 @@ import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch import org.eclipse.{lsp4j => l} +import scala.collection.concurrent.TrieMap class MetalsGlobal( settings: Settings, @@ -66,6 +67,8 @@ class MetalsGlobal( val logger: Logger = Logger.getLogger(classOf[MetalsGlobal].getName) + val richCompilationCache = TrieMap.empty[String, RichCompilationUnit] + class MetalsInteractiveAnalyzer(val global: compiler.type) extends InteractiveAnalyzer { @@ -100,6 +103,7 @@ class MetalsGlobal( else super.pluginsMacroExpand(typer, expandee, mode, pt) } } + override lazy val analyzer: this.MetalsInteractiveAnalyzer = new MetalsInteractiveAnalyzer(compiler) @@ -165,6 +169,16 @@ class MetalsGlobal( } } + /** + * If we run outline compilation on a file this means it's fresher + * and we don't want to get older data from last compilation. + * + * @param symbol symbol to check + */ + def isOutlinedFile(path: Path) = { + richCompilationCache.contains(path.toUri().toString()) + } + def workspaceSymbolListMembers( query: String, pos: Position, @@ -172,7 +186,7 @@ class MetalsGlobal( ): SymbolSearch.Result = { def isRelevantWorkspaceSymbol(sym: Symbol): Boolean = - sym.isStatic + sym.isStatic && !sym.isStale lazy val isInStringInterpolation = { lastVisitedParentTrees match { @@ -185,26 +199,31 @@ class MetalsGlobal( } } + def visitMember(sym: Symbol) = { + if (isRelevantWorkspaceSymbol(sym)) + visit { + if (isInStringInterpolation) + new WorkspaceInterpolationMember( + sym, + Nil, + edit => s"{$edit}", + None + ) + else + new WorkspaceMember(sym) + } + else false + } + + // TODO the same in autoimports TEST if (query.isEmpty) SymbolSearch.Result.INCOMPLETE else { val context = doLocateContext(pos) val visitor = new CompilerSearchVisitor( context, - sym => - if (isRelevantWorkspaceSymbol(sym)) - visit { - if (isInStringInterpolation) - new WorkspaceInterpolationMember( - sym, - Nil, - edit => s"{$edit}", - None - ) - else - new WorkspaceMember(sym) - } - else false + visitMember ) + searchOutline(visitMember, query) search.search(query, buildTargetIdentifier, visitor) } } @@ -214,12 +233,17 @@ class MetalsGlobal( pos: Position ): List[Member] = { val buffer = mutable.ListBuffer.empty[Member] + val isSeen = mutable.Set.empty[String] workspaceSymbolListMembers( query, pos, mem => { - buffer.append(mem) - true + val id = mem.sym.fullName + if (!isSeen(id)) { + isSeen += id + buffer.append(mem) + true + } else true } ) buffer.toList @@ -633,6 +657,23 @@ class MetalsGlobal( def CURSOR = "_CURSOR_" + def runOutline(files: List[m.pc.VirtualFileParams]) = { + this.settings.Youtline.value = true + files.foreach { params => + val unit = compiler.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None + ) + compiler.typeCheck(unit) + compiler.settings.Youtline.value = false + + // TODO make sure we recompile afterwards correctly + // TODO make sure it's cleared + compiler.richCompilationCache.put(params.uri().toString(), unit) + } + } + def addCompilationUnit( code: String, filename: String, @@ -664,7 +705,7 @@ class MetalsGlobal( } } - // Needed for 2.11 where `Name` doesn't extend CharSequence. + // // Needed for 2.11 where `Name` doesn't extend CharSequence. implicit def nameToCharSequence(name: Name): CharSequence = name.toString 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 3d82a6783c1..99b878ac658 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,11 +9,15 @@ 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] { - override def compiler(): MetalsGlobal = global + override def compiler(paths: List[VirtualFileParams]): MetalsGlobal = { + global.runOutline(paths) + global + } override def resetReporter(): Unit = global.reporter.reset() 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 d6417afc858..002ae3a2a06 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 @@ -3,6 +3,7 @@ package scala.meta.internal.pc import java.io.File import java.net.URI import java.nio.file.Path +import java.nio.file.Paths import java.util import java.util.Optional import java.util.concurrent.CompletableFuture @@ -12,6 +13,7 @@ import java.util.logging.Logger import java.{util => ju} import scala.collection.Seq +import scala.collection.concurrent.TrieMap import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContextExecutor import scala.reflect.io.VirtualDirectory @@ -19,6 +21,7 @@ import scala.tools.nsc.Settings import scala.tools.nsc.reporters.StoreReporter import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerVirtualFileParams import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.EmptyReportContext @@ -38,6 +41,9 @@ import scala.meta.pc.PresentationCompiler import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.RangeParams import scala.meta.pc.SymbolSearch +import scala.meta.pc.SyntheticDecoration +import scala.meta.pc.SyntheticDecorationsParams +import scala.meta.pc.CompilerFiles import scala.meta.pc.VirtualFileParams import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} @@ -50,6 +56,9 @@ import org.eclipse.lsp4j.Range import org.eclipse.lsp4j.SelectionRange import org.eclipse.lsp4j.SignatureHelp import org.eclipse.lsp4j.TextEdit +import scala.util.control.NonFatal +import scala.meta.internal.metals.Report +import java.nio.file.Files case class ScalaPresentationCompiler( buildTargetIdentifier: String = "", @@ -61,13 +70,17 @@ case class ScalaPresentationCompiler( sh: Option[ScheduledExecutorService] = None, config: PresentationCompilerConfig = PresentationCompilerConfigImpl(), folderPath: Option[Path] = None, - reportsLevel: ReportLevel = ReportLevel.Info + reportsLevel: ReportLevel = ReportLevel.Info, + compilerFiles: Option[CompilerFiles] = None ) extends PresentationCompiler { + private val wasCompiled = new util.concurrent.atomic.AtomicBoolean(false) + implicit val executionContext: ExecutionContextExecutor = ec val scalaVersion = BuildInfo.scalaCompilerVersion + private val changedDocuments = TrieMap.empty[URI, VirtualFileParams] val logger: Logger = Logger.getLogger(classOf[ScalaPresentationCompiler].getName) @@ -100,6 +113,11 @@ case class ScalaPresentationCompiler( ): PresentationCompiler = copy(sh = Some(sh)) + override def withCompilerFiles( + compilerFiles: CompilerFiles + ): PresentationCompiler = + copy(compilerFiles = Some(compilerFiles)) + override def withConfiguration( config: PresentationCompilerConfig ): PresentationCompiler = @@ -131,7 +149,43 @@ case class ScalaPresentationCompiler( compilerAccess.shutdown() } - override def restart(): Unit = { + override def restart(wasSuccesful: java.lang.Boolean): Unit = { + pprint.log(wasSuccesful) + compilerAccess + .withNonInterruptableCompiler(None)( + (), + EmptyCancelToken + ) { pc => + if (wasSuccesful) { + wasCompiled.set(true) + changedDocuments.clear() + } else { + /* we will still want outline recompiled if the compilation was not succesful */ + pc.compiler(Nil).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) + } + } + } + } + .get() + compilerAccess.shutdownCurrentCompiler() } @@ -149,13 +203,40 @@ case class ScalaPresentationCompiler( ) } + private def outlineFiles( + current: VirtualFileParams + ): List[VirtualFileParams] = { + if (!wasCompiled.getAndSet(true)) { + compilerFiles.iterator.flatMap(_.allPaths().asScala).foreach { path => + val uri = path.toUri() + if (!changedDocuments.contains(uri)) { + val text = Files.readString(path) + changedDocuments += uri -> CompilerOffsetParams(uri, text, 0) + } + } + } + val results = + changedDocuments.values.filterNot(_.uri() == current.uri()).toList + changedDocuments.clear() + changedDocuments += current.uri() -> current + results + } + override def didChange( params: VirtualFileParams ): CompletableFuture[ju.List[Diagnostic]] = { + changedDocuments += params.uri() -> params CompletableFuture.completedFuture(Nil.asJava) } - def didClose(uri: URI): Unit = {} + def didClose(uri: URI): Unit = { + compilerAccess.withNonInterruptableCompiler(None)( + (), + EmptyCancelToken + ) { pc => + pc.compiler(Nil).richCompilationCache.remove(uri.toString()) + } + } override def semanticTokens( params: VirtualFileParams @@ -166,7 +247,7 @@ case class ScalaPresentationCompiler( params.token ) { pc => new PcSemanticTokensProvider( - pc.compiler(), + pc.compiler(outlineFiles(params)), params ).provide().asJava } @@ -195,7 +276,8 @@ case class ScalaPresentationCompiler( EmptyCompletionList(), params.token ) { pc => - new CompletionProvider(pc.compiler(), params).completions() + new CompletionProvider(pc.compiler(outlineFiles(params)), params) + .completions() } override def implementAbstractMembers( @@ -206,7 +288,8 @@ case class ScalaPresentationCompiler( empty, params.token ) { pc => - new CompletionProvider(pc.compiler(), params).implementAll() + new CompletionProvider(pc.compiler(outlineFiles(params)), params) + .implementAll() } } @@ -218,7 +301,9 @@ case class ScalaPresentationCompiler( empty, params.token ) { pc => - new InferredTypeProvider(pc.compiler(), params).inferredTypeEdits().asJava + new InferredTypeProvider(pc.compiler(outlineFiles(params)), params) + .inferredTypeEdits() + .asJava } } @@ -228,7 +313,10 @@ case class ScalaPresentationCompiler( val empty: Either[String, List[TextEdit]] = Right(List()) (compilerAccess .withInterruptableCompiler(Some(params))(empty, params.token) { pc => - new PcInlineValueProviderImpl(pc.compiler(), params).getInlineTextEdits + new PcInlineValueProviderImpl( + pc.compiler(outlineFiles(params)), + params + ).getInlineTextEdits }) .thenApply { case Right(edits: List[TextEdit]) => edits.asJava @@ -244,7 +332,7 @@ case class ScalaPresentationCompiler( compilerAccess.withInterruptableCompiler(Some(range))(empty, range.token) { pc => new ExtractMethodProvider( - pc.compiler(), + pc.compiler(outlineFiles(range)), range, extractionPos ).extractMethod.asJava @@ -259,7 +347,7 @@ case class ScalaPresentationCompiler( (compilerAccess .withInterruptableCompiler(Some(params))(empty, params.token) { pc => new ConvertToNamedArgumentsProvider( - pc.compiler(), + pc.compiler(outlineFiles(params)), params, argIndices.asScala.map(_.toInt).toSet ).convertToNamedArguments @@ -279,7 +367,9 @@ case class ScalaPresentationCompiler( List.empty[AutoImportsResult].asJava, params.token ) { pc => - new AutoImportsProvider(pc.compiler(), name, params).autoImports().asJava + new AutoImportsProvider(pc.compiler(outlineFiles(params)), name, params) + .autoImports() + .asJava } override def getTasty( @@ -298,7 +388,7 @@ case class ScalaPresentationCompiler( ): CompletableFuture[CompletionItem] = CompletableFuture.completedFuture { compilerAccess.withSharedCompiler(None)(item) { pc => - new CompletionItemResolver(pc.compiler()).resolve(item, symbol) + new CompletionItemResolver(pc.compiler(Nil)).resolve(item, symbol) } } @@ -308,7 +398,10 @@ case class ScalaPresentationCompiler( compilerAccess.withNonInterruptableCompiler(Some(params))( new SignatureHelp(), params.token - ) { pc => new SignatureHelpProvider(pc.compiler()).signatureHelp(params) } + ) { pc => + new SignatureHelpProvider(pc.compiler(outlineFiles(params))) + .signatureHelp(params) + } override def prepareRename( params: OffsetParams @@ -317,7 +410,9 @@ case class ScalaPresentationCompiler( Optional.empty[Range](), params.token ) { pc => - new PcRenameProvider(pc.compiler(), params, None).prepareRename().asJava + new PcRenameProvider(pc.compiler(outlineFiles(params)), params, None) + .prepareRename() + .asJava } override def rename( @@ -328,7 +423,11 @@ case class ScalaPresentationCompiler( List[TextEdit]().asJava, params.token ) { pc => - new PcRenameProvider(pc.compiler(), params, Some(name)).rename().asJava + new PcRenameProvider( + pc.compiler(outlineFiles(params)), + params, + Some(name) + ).rename().asJava } override def hover( @@ -339,7 +438,7 @@ case class ScalaPresentationCompiler( params.token ) { pc => Optional.ofNullable( - new HoverProvider(pc.compiler(), params, config.hoverContentType()) + new HoverProvider(pc.compiler(outlineFiles(params)), params, config.hoverContentType()) .hover() .orNull ) @@ -350,7 +449,10 @@ case class ScalaPresentationCompiler( compilerAccess.withNonInterruptableCompiler(Some(params))( DefinitionResultImpl.empty, params.token - ) { pc => new PcDefinitionProvider(pc.compiler(), params).definition() } + ) { pc => + new PcDefinitionProvider(pc.compiler(outlineFiles(params)), params) + .definition() + } } override def info( @@ -374,7 +476,10 @@ case class ScalaPresentationCompiler( compilerAccess.withNonInterruptableCompiler(Some(params))( DefinitionResultImpl.empty, params.token - ) { pc => new PcDefinitionProvider(pc.compiler(), params).typeDefinition() } + ) { pc => + new PcDefinitionProvider(pc.compiler(outlineFiles(params)), params) + .typeDefinition() + } } override def documentHighlight( @@ -384,7 +489,9 @@ case class ScalaPresentationCompiler( List.empty[DocumentHighlight].asJava, params.token() ) { pc => - new PcDocumentHighlightProvider(pc.compiler(), params).highlights().asJava + new PcDocumentHighlightProvider(pc.compiler(outlineFiles(params)), params) + .highlights() + .asJava } override def semanticdbTextDocument( @@ -397,7 +504,7 @@ case class ScalaPresentationCompiler( EmptyCancelToken ) { pc => new SemanticdbTextDocumentProvider( - pc.compiler(), + pc.compiler(outlineFiles(CompilerOffsetParams(fileUri, code, 0))), config.semanticdbCompilerOptions().asScala.toList ) .textDocument(fileUri, code) @@ -412,7 +519,7 @@ case class ScalaPresentationCompiler( compilerAccess.withSharedCompiler(params.asScala.headOption)( List.empty[SelectionRange].asJava ) { pc => - new SelectionRangeProvider(pc.compiler(), params) + new SelectionRangeProvider(pc.compiler(Nil), params) .selectionRange() .asJava } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala index d377bf65599..e59f94e3a0d 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala @@ -2,6 +2,7 @@ package scala.meta.internal.pc import scala.meta.internal.metals.Fuzzy import scala.meta.internal.semanticdb.Scala._ +import java.nio.file.Path sealed abstract class SymbolSearchCandidate { final def nameLength(): Int = Fuzzy.nameLength(nameString) @@ -18,7 +19,8 @@ object SymbolSearchCandidate { override def packageString: String = pkg override def termCharacter: Char = '$' } - final case class Workspace(symbol: String) extends SymbolSearchCandidate { + final case class Workspace(symbol: String, path: Path) + extends SymbolSearchCandidate { def nameString: String = symbol override def packageString: String = { def loop(s: String): String = { 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 9f4ab5e53bb..fd757e6bdac 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 @@ -10,7 +10,35 @@ import scala.meta.pc.SymbolSearchVisitor import org.eclipse.{lsp4j => l} -trait WorkspaceSymbolSearch { this: MetalsGlobal => +trait WorkspaceSymbolSearch { compiler: MetalsGlobal => + + def searchOutline( + visitMember: Symbol => Boolean, + query: String + ) { + + def traverseUnit(unit: RichCompilationUnit) = { + + class Traverser extends compiler.Traverser { + override def traverse(tree: Tree): Unit = { + val sym = tree.symbol + def matches = if (sym.isType) + CompletionFuzzy.matchesSubCharacters(query, sym.name.toString()) + else CompletionFuzzy.matches(query, sym.name.toString()) + if (sym.exists && matches) { // && !sym.isStale + try { + visitMember(sym) + } catch { + case _: Throwable => + // with outline compiler there might be situations when things fail + } + } + } + } + unit.body.traverse(new Traverser) + } + compiler.richCompilationCache.values.foreach(traverseUnit) + } def info(symbol: String): Option[PcSymbolInformation] = { val index = symbol.lastIndexOf("/") @@ -97,6 +125,7 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => ) extends SymbolSearchVisitor { def visit(top: SymbolSearchCandidate): Int = { + var added = 0 for { sym <- loadSymbolFromClassfile(top) @@ -117,7 +146,7 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => kind: l.SymbolKind, range: l.Range ): Int = { - visit(SymbolSearchCandidate.Workspace(symbol)) + visit(SymbolSearchCandidate.Workspace(symbol, path)) } def shouldVisitPackage(pkg: String): Boolean = @@ -161,10 +190,14 @@ trait WorkspaceSymbolSearch { this: MetalsGlobal => } } members.filter(sym => isAccessible(sym)) - case SymbolSearchCandidate.Workspace(symbol) => + case SymbolSearchCandidate.Workspace(symbol, path) + if !compiler.isOutlinedFile(path) => + // pprint.log(path) val gsym = inverseSemanticdbSymbol(symbol) if (isAccessible(gsym)) gsym :: Nil else Nil + case _ => + Nil } } catch { case NonFatal(_) => Nil diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala index fc2ecbfb3a4..4662ff2ed55 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala @@ -1,11 +1,14 @@ package scala.meta.internal.pc import dotty.tools.dotc.reporting.StoreReporter +import scala.meta.pc.VirtualFileParams class Scala3CompilerWrapper(driver: MetalsDriver) extends CompilerWrapper[StoreReporter, MetalsDriver]: - override def compiler(): MetalsDriver = driver + override def compiler(paths: List[VirtualFileParams]): MetalsDriver = driver + + def compiler(): MetalsDriver = compiler(Nil) override def resetReporter(): Unit = val ctx = driver.currentCtx diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index 3d54d056a42..8c78e1a2c34 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -1,6 +1,8 @@ package tests import scala.concurrent.Future +import scala.meta.internal.metals.codeactions.ImportMissingSymbol +import scala.meta.internal.metals.codeactions.CreateNewSymbol class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { test("reset-pc") { @@ -55,4 +57,175 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { ) } yield () } + + test("non-compiling") { + cleanWorkspace() + for { + _ <- initialize( + """/metals.json + |{ + | "a": {} + |} + |/a/src/main/scala/a/A.scala + |package a + |class A { + | // @@ + | def completeThisUniqueName() = 42 + |} + |/a/src/main/scala/b/B.scala + |package b + |object UniqueObject { + | def completeThisUniqueName() = 42 + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/A.scala") + _ <- server.didOpen("a/src/main/scala/b/B.scala") + _ = assertNoDiagnostics() + // break the file and add a new method, should show methods + _ <- + server.didChange("a/src/main/scala/b/B.scala") { _ => + """|package b + |object UniqueObject{ + | def completeThisUniqueName() = 42 + | def completeThisUniqueName2(): String = 42 + |}""".stripMargin + } + _ <- assertCompletion( + "b.UniqueObject.completeThisUniqueNa@@", + """|completeThisUniqueName(): Int + |completeThisUniqueName2(): String""".stripMargin, + ) + // make sure autoimports are properly suggested + _ <- assertCompletionEdit( + "UniqueObject@@", + """|package a + | + |import b.UniqueObject + |class A { + | UniqueObject + | def completeThisUniqueName() = 42 + |} + |""".stripMargin, + ) + // change the neame of the object and test again + _ <- + server.didChange("a/src/main/scala/b/B.scala") { _ => + """|package b + |object UniqueObjectOther{ + | def completeThisUniqueName() = 42 + | def completeThisUniqueName2(): String = 42 + |}""".stripMargin + } + _ <- assertCompletion( + "b.UniqueObjectOther.completeThisUniqueNa@@", + """|completeThisUniqueName(): Int + |completeThisUniqueName2(): String""".stripMargin, + ) + // make sure old name is not suggested + _ <- assertCompletionEdit( + "UniqueObject@@", + """|package a + | + |import b.UniqueObjectOther + |class A { + | UniqueObjectOther + | def completeThisUniqueName() = 42 + |} + |""".stripMargin, + ) + // check if the change name is piecked up despite the file not compiling + newText = """|package a + | + |class A { + | <> + | def completeThisUniqueName() = 42 + |} + |""".stripMargin + input = newText.replace("<<", "").replace(">>", "") + _ <- server.didSave("a/src/main/scala/a/A.scala") { _ => + newText.replace("<<", "").replace(">>", "") + } + codeActions <- + server + .assertCodeAction( + "a/src/main/scala/a/A.scala", + newText, + s"""|${ImportMissingSymbol.title("UniqueObjectOther", "b")} + |${CreateNewSymbol.title("UniqueObjectOther")} + |""".stripMargin, + kind = Nil, + ) + // make sure that the now change UniqueObject is not suggested + _ <- server.didSave("a/src/main/scala/a/A.scala") { _ => + input.replace("UniqueObjectOther", "UniqueObject") + } + codeActions <- + server + .assertCodeAction( + "a/src/main/scala/a/A.scala", + newText.replace("UniqueObjectOther", "UniqueObject"), + s"""|${CreateNewSymbol.title("UniqueObject")} + |""".stripMargin, + kind = Nil, + ) + } yield () + } + + test("never-compiling") { + cleanWorkspace() + for { + _ <- initialize( + """/metals.json + |{ + | "a": {} + |} + |/a/src/main/scala/a/A.scala + |package a + |class A { + | // @@ + | def completeThisUniqueName(): String = 42 + |} + |/a/src/main/scala/b/B.scala + |package b + |object UniqueObject { + | def completeThisUniqueName() = 42 + | def completeThisUniqueName2(): String = 42 + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/A.scala") + _ <- server.didOpen("a/src/main/scala/b/B.scala") + _ = assertNoDiff( + server.client.workspaceDiagnostics, + """|a/src/main/scala/a/A.scala:4:42: error: type mismatch; + | found : Int(42) + | required: String + | def completeThisUniqueName(): String = 42 + | ^^ + |a/src/main/scala/b/B.scala:4:43: error: type mismatch; + | found : Int(42) + | required: String + | def completeThisUniqueName2(): String = 42 + | ^^ + |""".stripMargin, + ) + _ <- assertCompletion( + "b.UniqueObject.completeThisUniqueNa@@", + """|completeThisUniqueName(): Int + |completeThisUniqueName2(): String""".stripMargin, + ) + _ <- assertCompletionEdit( + "UniqueObject@@", + """|package a + | + |import b.UniqueObject + |class A { + | UniqueObject + | def completeThisUniqueName(): String = 42 + |} + |""".stripMargin, + ) + } yield () + } } From d5d585781b3706129692738605b6658e18aae966 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 8 Feb 2024 19:05:35 +0100 Subject: [PATCH 02/20] more outline compiler progress --- .../meta/internal/metals/Compilers.scala | 8 +-- .../java/scala/meta/pc/DefinitionResult.java | 2 +- .../scala/meta/pc/PresentationCompiler.java | 10 +-- .../pc/JavaPresentationCompiler.scala | 1 - .../meta/internal/pc/CompilerWrapper.scala | 2 + .../scala/meta/internal/pc/MetalsGlobal.scala | 34 ++++++---- .../pc/PcSemanticTokensProvider.scala | 3 +- .../pc/ScalaPresentationCompiler.scala | 11 ++-- .../internal/pc/SymbolSearchCandidate.scala | 3 +- .../internal/pc/WorkspaceSymbolSearch.scala | 1 - .../internal/pc/Scala3CompilerWrapper.scala | 5 +- .../pc/ScalaPresentationCompiler.scala | 2 +- .../test/scala/tests/CompilersLspSuite.scala | 62 ++++++++++++++++++- .../scala/tests/SemanticTokensLspSuite.scala | 51 +++++++++++++++ 14 files changed, 156 insertions(+), 39 deletions(-) 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 f65dfd02359..e9d10ec8c69 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -102,8 +102,7 @@ class Compilers( import compilerConfiguration._ - // TODO - // val wasPreviouslyCompiledSuccesfully = new ConcurrentHashSet[BuildTargetIdentifier] + val wasPreviouslyCompiledSuccessfully: ju.Set[BuildTargetIdentifier] = ConcurrentHashSet.empty[BuildTargetIdentifier] class TargetCompilerFiles(targetId: BuildTargetIdentifier) extends CompilerFiles { @@ -259,16 +258,17 @@ class Compilers( def didCompile(report: CompileReport): Unit = { if (report.getErrors > 0) { buildTargetPCFromCache(report.getTarget).foreach(_.restart(false)) + wasPreviouslyCompiledSuccessfully.remove(report.getTarget) } else { // Restart PC for all build targets that depend on this target since the classfiles // may have changed. - // TODO should mark as succesful + wasPreviouslyCompiledSuccessfully.add(report.getTarget) for { target <- buildTargets.allInverseDependencies(report.getTarget) compiler <- buildTargetPCFromCache(target) } { - compiler.restart(true) + compiler.restart(wasPreviouslyCompiledSuccessfully.contains(target)) } } } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/DefinitionResult.java b/mtags-interfaces/src/main/java/scala/meta/pc/DefinitionResult.java index a8026e8a62d..bab22cac17f 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/DefinitionResult.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/DefinitionResult.java @@ -6,4 +6,4 @@ public interface DefinitionResult { String symbol(); List locations(); -} \ No newline at end of file +} 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 c8e6ba6f458..b31d1ed2047 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -161,7 +161,7 @@ public abstract CompletableFuture> convertToNamedArguments(Offset List argIndices); /** - * The text contents of the fiven file changed. + * The text contents of the given file changed. */ public abstract CompletableFuture> didChange(VirtualFileParams params); @@ -220,14 +220,14 @@ public CompletableFuture> info(String symbol) { /** * Clean the symbol table and other mutable state in the compiler. */ - public abstract void restart(Boolean successfulCompilation); + public void restart(Boolean successfulCompilation) { + restart(); + } /** * Clean the symbol table and other mutable state in the compiler. */ - public void restart(){ - restart(true); - } + public abstract void restart(); /** * Set logger level for reports. diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala index a9d21f7a202..17239c2a7cc 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala @@ -44,7 +44,6 @@ case class JavaPresentationCompiler( workspace: Option[Path] = None ) extends PresentationCompiler { - override def restart(x$1: lang.Boolean): Unit = () private val javaCompiler = new JavaMetalsGlobal(search, config, classpath) override def complete( 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 a8fb6bd730e..481d5e7c898 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 @@ -16,6 +16,8 @@ trait CompilerWrapper[Reporter, Compiler] { def compiler(changeFiles: List[VirtualFileParams]): Compiler + def compiler(): Compiler = compiler(Nil) + def presentationCompilerThread: Option[Thread] } 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 a08013aee4a..2639caddb75 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 @@ -5,6 +5,7 @@ import java.util import java.util.logging.Logger import java.{util => ju} +import scala.collection.concurrent.TrieMap import scala.collection.mutable import scala.language.implicitConversions import scala.reflect.internal.util.Position @@ -30,7 +31,6 @@ import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch import org.eclipse.{lsp4j => l} -import scala.collection.concurrent.TrieMap class MetalsGlobal( settings: Settings, @@ -67,7 +67,10 @@ class MetalsGlobal( val logger: Logger = Logger.getLogger(classOf[MetalsGlobal].getName) - val richCompilationCache = 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] class MetalsInteractiveAnalyzer(val global: compiler.type) extends InteractiveAnalyzer { @@ -175,7 +178,7 @@ class MetalsGlobal( * * @param symbol symbol to check */ - def isOutlinedFile(path: Path) = { + def isOutlinedFile(path: Path): Boolean = { richCompilationCache.contains(path.toUri().toString()) } @@ -657,28 +660,27 @@ class MetalsGlobal( def CURSOR = "_CURSOR_" - def runOutline(files: List[m.pc.VirtualFileParams]) = { - this.settings.Youtline.value = true + def runOutline(files: List[m.pc.VirtualFileParams]): Unit = { + compiler.settings.Youtline.value = true files.foreach { params => val unit = compiler.addCompilationUnit( params.text(), params.uri.toString(), - cursor = None + cursor = None, + isOutline = true ) compiler.typeCheck(unit) - compiler.settings.Youtline.value = false - - // TODO make sure we recompile afterwards correctly - // TODO make sure it's cleared compiler.richCompilationCache.put(params.uri().toString(), unit) } + compiler.settings.Youtline.value = false } def addCompilationUnit( code: String, filename: String, cursor: Option[Int], - cursorName: String = CURSOR + cursorName: String = CURSOR, + isOutline: Boolean = false, ): RichCompilationUnit = { val codeWithCursor = cursor match { case Some(offset) => @@ -697,15 +699,21 @@ class MetalsGlobal( if util.Arrays.equals( value.source.content, richUnit.source.content - ) => + ) && (isOutline || fullyCompiled(filename)) => value case _ => unitOfFile(richUnit.source.file) = richUnit + if(!isOutline) { + fullyCompiled += filename + } else { + fullyCompiled -= filename + } + richUnit } } - // // Needed for 2.11 where `Name` doesn't extend CharSequence. + // Needed for 2.11 where `Name` doesn't extend CharSequence. implicit def nameToCharSequence(name: Name): CharSequence = name.toString diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala index b7cd7c1ef7d..28b2cdfd1cf 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/PcSemanticTokensProvider.scala @@ -69,7 +69,7 @@ final class PcSemanticTokensProvider( } } } - def provide(): List[Node] = + def provide(): List[Node] = { Collector .result() .flatten @@ -77,6 +77,7 @@ final class PcSemanticTokensProvider( if (n1.start() == n2.start()) n1.end() < n2.end() else n1.start() < n2.start() ) + } def makeNode( sym: Collector.compiler.Symbol, 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 002ae3a2a06..d3dea4e0fee 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 @@ -2,8 +2,8 @@ package scala.meta.internal.pc import java.io.File import java.net.URI +import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths import java.util import java.util.Optional import java.util.concurrent.CompletableFuture @@ -19,18 +19,21 @@ import scala.concurrent.ExecutionContextExecutor import scala.reflect.io.VirtualDirectory import scala.tools.nsc.Settings import scala.tools.nsc.reporters.StoreReporter +import scala.util.control.NonFatal import scala.meta.internal.jdk.CollectionConverters._ import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerVirtualFileParams import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.EmptyReportContext +import scala.meta.internal.metals.Report import scala.meta.internal.metals.ReportContext import scala.meta.internal.metals.ReportLevel import scala.meta.internal.metals.StdReportContext import scala.meta.internal.mtags.BuildInfo import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.pc.AutoImportsResult +import scala.meta.pc.CompilerFiles import scala.meta.pc.DefinitionResult import scala.meta.pc.DisplayableException import scala.meta.pc.HoverSignature @@ -43,7 +46,6 @@ import scala.meta.pc.RangeParams import scala.meta.pc.SymbolSearch import scala.meta.pc.SyntheticDecoration import scala.meta.pc.SyntheticDecorationsParams -import scala.meta.pc.CompilerFiles import scala.meta.pc.VirtualFileParams import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} @@ -56,9 +58,6 @@ import org.eclipse.lsp4j.Range import org.eclipse.lsp4j.SelectionRange import org.eclipse.lsp4j.SignatureHelp import org.eclipse.lsp4j.TextEdit -import scala.util.control.NonFatal -import scala.meta.internal.metals.Report -import java.nio.file.Files case class ScalaPresentationCompiler( buildTargetIdentifier: String = "", @@ -149,8 +148,8 @@ case class ScalaPresentationCompiler( compilerAccess.shutdown() } + def restart(): Unit = restart(true) override def restart(wasSuccesful: java.lang.Boolean): Unit = { - pprint.log(wasSuccesful) compilerAccess .withNonInterruptableCompiler(None)( (), diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala index e59f94e3a0d..70e321f7173 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/SymbolSearchCandidate.scala @@ -1,8 +1,9 @@ package scala.meta.internal.pc +import java.nio.file.Path + import scala.meta.internal.metals.Fuzzy import scala.meta.internal.semanticdb.Scala._ -import java.nio.file.Path sealed abstract class SymbolSearchCandidate { final def nameLength(): Int = Fuzzy.nameLength(nameString) 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 fd757e6bdac..fc6e2755caf 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 @@ -192,7 +192,6 @@ trait WorkspaceSymbolSearch { compiler: MetalsGlobal => members.filter(sym => isAccessible(sym)) case SymbolSearchCandidate.Workspace(symbol, path) if !compiler.isOutlinedFile(path) => - // pprint.log(path) val gsym = inverseSemanticdbSymbol(symbol) if (isAccessible(gsym)) gsym :: Nil else Nil diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala index 4662ff2ed55..b12d761f07f 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala @@ -1,15 +1,14 @@ package scala.meta.internal.pc -import dotty.tools.dotc.reporting.StoreReporter import scala.meta.pc.VirtualFileParams +import dotty.tools.dotc.reporting.StoreReporter + class Scala3CompilerWrapper(driver: MetalsDriver) extends CompilerWrapper[StoreReporter, MetalsDriver]: override def compiler(paths: List[VirtualFileParams]): MetalsDriver = driver - def compiler(): MetalsDriver = compiler(Nil) - override def resetReporter(): Unit = val ctx = driver.currentCtx ctx.reporter.removeBufferedMessages(using ctx) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index e1fe6415fb4..936d2477314 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -182,7 +182,7 @@ case class ScalaPresentationCompiler( def shutdown(): Unit = compilerAccess.shutdown() - def restart(): Unit = + override def restart(): Unit = compilerAccess.shutdownCurrentCompiler() def diagnosticsForDebuggingPurposes(): ju.List[String] = diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index 8c78e1a2c34..79b41a5cdb6 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -1,8 +1,9 @@ package tests import scala.concurrent.Future -import scala.meta.internal.metals.codeactions.ImportMissingSymbol + import scala.meta.internal.metals.codeactions.CreateNewSymbol +import scala.meta.internal.metals.codeactions.ImportMissingSymbol class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { test("reset-pc") { @@ -108,7 +109,7 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { |} |""".stripMargin, ) - // change the neame of the object and test again + // change the name of the object and test again _ <- server.didChange("a/src/main/scala/b/B.scala") { _ => """|package b @@ -228,4 +229,61 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { ) } yield () } + + test("never-compiling-reverse-order".ignore) { + cleanWorkspace() + for { + _ <- initialize( + """/metals.json + |{ + | "a": {} + |} + |/a/src/main/scala/a/A.scala + |package a + |class A { + | // @@ + | def completeThisUniqueName(): String = 42 + |} + |/a/src/main/scala/b/B.scala + |package b + |trait BTrait { + | def completeThisUniqueName3() = 42 + |} + |class B + |/a/src/main/scala/c/C.scala + |package c + |import b.BTrait + |import b.B + | + |object UniqueObject extends BTrait { + | def completeThisUniqueName() = 42 + | def completeThisUniqueName2(b: B): String = 42 + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/A.scala") + _ <- server.didOpen("a/src/main/scala/b/B.scala") + _ <- server.didOpen("a/src/main/scala/c/C.scala") + _ = assertNoDiff( + server.client.workspaceDiagnostics, + """|a/src/main/scala/a/A.scala:4:42: error: type mismatch; + | found : Int(42) + | required: String + | def completeThisUniqueName(): String = 42 + | ^^ + |a/src/main/scala/c/C.scala:7:47: error: type mismatch; + | found : Int(42) + | required: String + | def completeThisUniqueName2(b: B): String = 42 + | ^^ + |""".stripMargin, + ) + _ <- assertCompletion( + "c.UniqueObject.completeThisUniqueNa@@", + """|completeThisUniqueName(): Int + |completeThisUniqueName2(b: B): String + |completeThisUniqueName3(): Int""".stripMargin, + ) + } yield () + } } diff --git a/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala b/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala index e30699ba51b..6ec2131e181 100644 --- a/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala +++ b/tests/unit/src/test/scala/tests/SemanticTokensLspSuite.scala @@ -254,6 +254,57 @@ class SemanticTokensLspSuite extends BaseLspSuite("SemanticTokens") { |""".stripMargin, ) + test("new-changes") { + val expected = + """|<>/*keyword*/ <>/*namespace*/ + |<>/*keyword*/ <
>/*class*/ { + | <>/*keyword*/ <>/*method,definition*/: <>/*type*/ = { + | <>/*keyword*/ <>/*variable,definition,readonly*/ = <<3>>/*number*/ + | <>/*variable,readonly*/ <<+>>/*method,deprecated,abstract*/ <<4>>/*number*/ + |""".stripMargin + val fileContent = expected.replaceAll(raw"<<|>>|/\*.*?\*/", "") + 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, + expectError = true, + ) + _ <- server.didChangeConfiguration( + """{ + | "enable-semantic-highlighting": true + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/Main.scala") + _ <- server.didChange("a/src/main/scala/a/Main.scala")(_ => fileContent) + _ <- server.didSave("a/src/main/scala/a/Main.scala")(identity) + _ <- server.didOpen("a/src/main/scala/a/OtherFile.scala") + // triggers outline compile on `Main.scala` + _ <- server.assertSemanticHighlight( + "a/src/main/scala/a/OtherFile.scala", + """|<>/*keyword*/ <>/*namespace*/ + |<>/*keyword*/ <>/*class*/ + |""".stripMargin, + """|package a + |object A + |""".stripMargin, + ) + _ <- server.didOpen("a/src/main/scala/a/Main.scala") + // tests if we do full compile after outline compile + _ <- server.assertSemanticHighlight( + "a/src/main/scala/a/Main.scala", + expected, + fileContent, + ) + } yield () + } + def check( name: TestOptions, expected: String, From 401d6bfd4037a5c1c37dacf63ec27b51bf15e28e Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 12 Feb 2024 15:40:19 +0100 Subject: [PATCH 03/20] add new files for outline --- .../scala/scala/meta/internal/metals/MetalsLspService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index dd260530436..532b5e6fc4f 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -403,7 +403,7 @@ abstract class MetalsLspService( scalaVersionSelector, clientConfig.icons, onCreate = path => { - buildTargets.onCreate(path) + onCreate(path) onChange(List(path)) }, ) From 8d9d235f60f693633c311d585c25379cc081b496 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 12 Feb 2024 19:04:06 +0100 Subject: [PATCH 04/20] adjust for all Scala 2 versions --- .../scala/meta/internal/pc/Compat.scala | 5 +++ .../scala/meta/internal/pc/Compat.scala | 16 +++++++++ .../scala/meta/internal/pc/Compat.scala | 16 +++++++++ .../scala/meta/internal/pc/MetalsGlobal.scala | 17 +--------- .../internal/pc/WorkspaceSymbolSearch.scala | 34 +++++++++++-------- 5 files changed, 57 insertions(+), 31 deletions(-) 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 b3b68c9534e..3d2936e86f8 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,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 => def metalsFunctionArgTypes(tpe: Type): List[Type] = { @@ -19,4 +20,8 @@ trait Compat { this: MetalsGlobal => def isAliasCompletion(m: Member): Boolean = false def constantType(c: ConstantType): ConstantType = c + + def runOutline(files: List[VirtualFileParams]): 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 da02d75b0f9..772066b055f 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 => def metalsFunctionArgTypes(tpe: Type): List[Type] = @@ -16,4 +17,19 @@ trait Compat { this: MetalsGlobal => def isAliasCompletion(m: Member): Boolean = false def constantType(c: ConstantType): ConstantType = c + + def runOutline(files: List[VirtualFileParams]): Unit = { + this.settings.Youtline.value = true + files.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true + ) + 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 a4eeed4b00c..2bc76c7d70c 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 @@ -2,6 +2,7 @@ package scala.meta.internal.pc import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.pc.VirtualFileParams trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = @@ -23,4 +24,19 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: List[VirtualFileParams]): Unit = { + this.settings.Youtline.value = true + files.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true + ) + 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 2639caddb75..2fd39580d77 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 @@ -660,27 +660,12 @@ class MetalsGlobal( def CURSOR = "_CURSOR_" - def runOutline(files: List[m.pc.VirtualFileParams]): Unit = { - compiler.settings.Youtline.value = true - files.foreach { params => - val unit = compiler.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true - ) - compiler.typeCheck(unit) - compiler.richCompilationCache.put(params.uri().toString(), unit) - } - compiler.settings.Youtline.value = false - } - def addCompilationUnit( code: String, filename: String, cursor: Option[Int], cursorName: String = CURSOR, - isOutline: Boolean = false, + isOutline: Boolean = false ): RichCompilationUnit = { val codeWithCursor = cursor match { case Some(offset) => 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 fc6e2755caf..e614f0c75fa 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 @@ -9,6 +9,7 @@ import scala.meta.pc.PcSymbolProperty import scala.meta.pc.SymbolSearchVisitor import org.eclipse.{lsp4j => l} +import scala.annotation.tailrec trait WorkspaceSymbolSearch { compiler: MetalsGlobal => @@ -18,24 +19,27 @@ trait WorkspaceSymbolSearch { compiler: MetalsGlobal => ) { def traverseUnit(unit: RichCompilationUnit) = { - - class Traverser extends compiler.Traverser { - override def traverse(tree: Tree): Unit = { - val sym = tree.symbol - def matches = if (sym.isType) - CompletionFuzzy.matchesSubCharacters(query, sym.name.toString()) - else CompletionFuzzy.matches(query, sym.name.toString()) - if (sym.exists && matches) { // && !sym.isStale - try { - visitMember(sym) - } catch { - case _: Throwable => - // with outline compiler there might be situations when things fail + @tailrec + def loop(trees: List[Tree]): Unit = { + trees match { + case Nil => + case tree :: tail => + val sym = tree.symbol + def matches = if (sym.isType) + CompletionFuzzy.matchesSubCharacters(query, sym.name.toString()) + else CompletionFuzzy.matches(query, sym.name.toString()) + if (sym.exists && matches) { + try { + visitMember(sym) + } catch { + case _: Throwable => + // with outline compiler there might be situations when things fail + } } - } + loop(tree.children ++ tail) } } - unit.body.traverse(new Traverser) + loop(List(unit.body)) } compiler.richCompilationCache.values.foreach(traverseUnit) } From 7e4b45ec0e82da462c1e4219d73f8dfd64446e56 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 13 Feb 2024 10:24:46 +0100 Subject: [PATCH 05/20] don't outline before first compilation is done --- .../meta/internal/metals/Compilers.scala | 14 ++-- .../scala/meta/internal/pc/Compat.scala | 1 + .../pc/ScalaPresentationCompiler.scala | 70 +++++++++++-------- .../internal/pc/WorkspaceSymbolSearch.scala | 2 +- 4 files changed, 49 insertions(+), 38 deletions(-) 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 e9d10ec8c69..3acfb5c2f51 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -102,8 +102,6 @@ class Compilers( import compilerConfiguration._ - val wasPreviouslyCompiledSuccessfully: ju.Set[BuildTargetIdentifier] = ConcurrentHashSet.empty[BuildTargetIdentifier] - class TargetCompilerFiles(targetId: BuildTargetIdentifier) extends CompilerFiles { override def allPaths(): ju.List[Path] = { @@ -256,19 +254,19 @@ class Compilers( } def didCompile(report: CompileReport): Unit = { - if (report.getErrors > 0) { - buildTargetPCFromCache(report.getTarget).foreach(_.restart(false)) - wasPreviouslyCompiledSuccessfully.remove(report.getTarget) - } else { + val isSuccessful = report.getErrors == 0 + buildTargetPCFromCache(report.getTarget).foreach(_.restart(isSuccessful)) + + if (isSuccessful) { // Restart PC for all build targets that depend on this target since the classfiles // may have changed. - wasPreviouslyCompiledSuccessfully.add(report.getTarget) for { target <- buildTargets.allInverseDependencies(report.getTarget) + if target != report.getTarget compiler <- buildTargetPCFromCache(target) } { - compiler.restart(wasPreviouslyCompiledSuccessfully.contains(target)) + compiler.restart() } } } 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 2bc76c7d70c..0faa6d3eb31 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 @@ -2,6 +2,7 @@ package scala.meta.internal.pc import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter + import scala.meta.pc.VirtualFileParams trait Compat { this: 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 d3dea4e0fee..9b28fa86e49 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 @@ -73,7 +73,7 @@ case class ScalaPresentationCompiler( compilerFiles: Option[CompilerFiles] = None ) extends PresentationCompiler { - private val wasCompiled = new util.concurrent.atomic.AtomicBoolean(false) + private val wasFirstCompilationSuccessful = new util.concurrent.atomic.AtomicReference[Option[Boolean]](None) implicit val executionContext: ExecutionContextExecutor = ec @@ -148,39 +148,49 @@ case class ScalaPresentationCompiler( compilerAccess.shutdown() } - def restart(): Unit = restart(true) - override def restart(wasSuccesful: java.lang.Boolean): Unit = { + def restart(): Unit = restoreOutlineAndRestart() + + override def restart(wasSuccessful: java.lang.Boolean): Unit = { + wasFirstCompilationSuccessful.updateAndGet { + case None => Some(wasSuccessful) + case value => value + } + + if(wasSuccessful) { + changedDocuments.clear() + compilerAccess.shutdownCurrentCompiler() + } else { + restoreOutlineAndRestart() + } + } + + private def restoreOutlineAndRestart() = { compilerAccess .withNonInterruptableCompiler(None)( (), EmptyCancelToken ) { pc => - if (wasSuccesful) { - wasCompiled.set(true) - changedDocuments.clear() - } else { - /* we will still want outline recompiled if the compilation was not succesful */ - pc.compiler(Nil).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 - ) + /* we will still want outline recompiled if the compilation was not succesful */ + pc.compiler(Nil).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() @@ -205,7 +215,8 @@ case class ScalaPresentationCompiler( private def outlineFiles( current: VirtualFileParams ): List[VirtualFileParams] = { - if (!wasCompiled.getAndSet(true)) { + if (wasFirstCompilationSuccessful.getAndSet(Some(true)).exists(!_)) { + // if first compilation was unsuccessful we want to outline all files compilerFiles.iterator.flatMap(_.allPaths().asScala).foreach { path => val uri = path.toUri() if (!changedDocuments.contains(uri)) { @@ -214,6 +225,7 @@ case class ScalaPresentationCompiler( } } } + val results = changedDocuments.values.filterNot(_.uri() == current.uri()).toList changedDocuments.clear() 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 e614f0c75fa..ffd5c4d56d7 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 @@ -2,6 +2,7 @@ package scala.meta.internal.pc import java.nio.file.Path +import scala.annotation.tailrec import scala.util.control.NonFatal import scala.meta.pc.PcSymbolKind @@ -9,7 +10,6 @@ import scala.meta.pc.PcSymbolProperty import scala.meta.pc.SymbolSearchVisitor import org.eclipse.{lsp4j => l} -import scala.annotation.tailrec trait WorkspaceSymbolSearch { compiler: MetalsGlobal => From 1c161df5b20318a5c91173cb5756f023b94cf003 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 13 Feb 2024 10:56:18 +0100 Subject: [PATCH 06/20] outline twice for first compile --- .../meta/internal/pc/CompilerWrapper.scala | 13 +++++-- .../scala/meta/internal/pc/Compat.scala | 12 ++++++- .../internal/pc/ScalaCompilerAccess.scala | 4 +-- .../pc/ScalaPresentationCompiler.scala | 35 ++++++++++--------- .../internal/pc/Scala3CompilerWrapper.scala | 4 +-- .../test/scala/tests/CompilersLspSuite.scala | 2 +- 6 files changed, 45 insertions(+), 25 deletions(-) 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 481d5e7c898..a6932b9d02e 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 @@ -14,10 +14,19 @@ trait CompilerWrapper[Reporter, Compiler] { def stop(): Unit - def compiler(changeFiles: List[VirtualFileParams]): Compiler + def compiler(changeFiles: OutlineFiles): Compiler - def compiler(): Compiler = compiler(Nil) + def compiler(): Compiler = compiler(OutlineFiles.empty) def presentationCompilerThread: Option[Thread] } + +case class OutlineFiles( + files: List[VirtualFileParams], + firstCompileSubstitute: Boolean = false +) + +object OutlineFiles { + def empty = OutlineFiles(Nil) +} 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 0faa6d3eb31..eea56572e90 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 @@ -26,7 +26,17 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c - def runOutline(files: List[VirtualFileParams]): Unit = { + def runOutline(files: OutlineFiles): Unit = { + 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) + } + } + + private def runOutline(files: List[VirtualFileParams], forceNewUnit: Boolean = false): Unit = { this.settings.Youtline.value = true files.foreach { params => val unit = this.addCompilationUnit( 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 99b878ac658..af9681f0054 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 @@ -14,8 +14,8 @@ import scala.meta.pc.VirtualFileParams class ScalaCompilerWrapper(global: MetalsGlobal) extends CompilerWrapper[StoreReporter, MetalsGlobal] { - override def compiler(paths: List[VirtualFileParams]): MetalsGlobal = { - global.runOutline(paths) + override def compiler(files: OutlineFiles): MetalsGlobal = { + global.runOutline(files) global } 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 9b28fa86e49..1c8855635ea 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 @@ -171,7 +171,7 @@ case class ScalaPresentationCompiler( EmptyCancelToken ) { pc => /* we will still want outline recompiled if the compilation was not succesful */ - pc.compiler(Nil).richCompilationCache.foreach { + pc.compiler().richCompilationCache.foreach { case (uriString, unit) => try { val text = unit.source.content.mkString @@ -214,23 +214,26 @@ case class ScalaPresentationCompiler( private def outlineFiles( current: VirtualFileParams - ): List[VirtualFileParams] = { - if (wasFirstCompilationSuccessful.getAndSet(Some(true)).exists(!_)) { - // if first compilation was unsuccessful we want to outline all files - compilerFiles.iterator.flatMap(_.allPaths().asScala).foreach { path => - val uri = path.toUri() - if (!changedDocuments.contains(uri)) { - val text = Files.readString(path) - changedDocuments += uri -> CompilerOffsetParams(uri, text, 0) + ): OutlineFiles = { + val result = + if (wasFirstCompilationSuccessful.getAndSet(Some(true)).exists(!_)) { + // if first compilation was unsuccessful we want to outline all files + compilerFiles.iterator.flatMap(_.allPaths().asScala).foreach { path => + val uri = path.toUri() + if (!changedDocuments.contains(uri)) { + val text = Files.readString(path) + changedDocuments += uri -> CompilerOffsetParams(uri, text, 0) + } } + OutlineFiles(changedDocuments.values.toList, firstCompileSubstitute = true) + } else { + val files = changedDocuments.values.filterNot(_.uri() == current.uri()).toList + OutlineFiles(files) } - } - val results = - changedDocuments.values.filterNot(_.uri() == current.uri()).toList changedDocuments.clear() changedDocuments += current.uri() -> current - results + result } override def didChange( @@ -245,7 +248,7 @@ case class ScalaPresentationCompiler( (), EmptyCancelToken ) { pc => - pc.compiler(Nil).richCompilationCache.remove(uri.toString()) + pc.compiler().richCompilationCache.remove(uri.toString()) } } @@ -399,7 +402,7 @@ case class ScalaPresentationCompiler( ): CompletableFuture[CompletionItem] = CompletableFuture.completedFuture { compilerAccess.withSharedCompiler(None)(item) { pc => - new CompletionItemResolver(pc.compiler(Nil)).resolve(item, symbol) + new CompletionItemResolver(pc.compiler()).resolve(item, symbol) } } @@ -530,7 +533,7 @@ case class ScalaPresentationCompiler( compilerAccess.withSharedCompiler(params.asScala.headOption)( List.empty[SelectionRange].asJava ) { pc => - new SelectionRangeProvider(pc.compiler(Nil), params) + new SelectionRangeProvider(pc.compiler(), params) .selectionRange() .asJava } diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala index b12d761f07f..da2cadf11d5 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala @@ -1,13 +1,11 @@ package scala.meta.internal.pc -import scala.meta.pc.VirtualFileParams - import dotty.tools.dotc.reporting.StoreReporter class Scala3CompilerWrapper(driver: MetalsDriver) extends CompilerWrapper[StoreReporter, MetalsDriver]: - override def compiler(paths: List[VirtualFileParams]): MetalsDriver = driver + override def compiler(paths: OutlineFiles): MetalsDriver = driver override def resetReporter(): Unit = val ctx = driver.currentCtx diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index 79b41a5cdb6..142f0476770 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -230,7 +230,7 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { } yield () } - test("never-compiling-reverse-order".ignore) { + test("never-compiling-reverse-order") { cleanWorkspace() for { _ <- initialize( From 28a6f7abdb1e28b7abafeab4e3daf721324b1fdb Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 13 Feb 2024 13:51:53 +0100 Subject: [PATCH 07/20] test fixes --- .../meta/internal/metals/Compilers.scala | 7 ++ .../scala/meta/pc/PresentationCompiler.java | 6 +- .../meta/internal/pc/CompilerWrapper.scala | 2 +- .../scala/meta/internal/pc/Compat.scala | 3 +- .../scala/meta/internal/pc/Compat.scala | 21 ++++- .../scala/meta/internal/pc/Compat.scala | 14 ++-- .../scala/meta/internal/pc/MetalsGlobal.scala | 10 ++- .../internal/pc/ScalaCompilerAccess.scala | 1 - .../pc/ScalaPresentationCompiler.scala | 79 ++++++++++++------- .../internal/pc/WorkspaceSymbolSearch.scala | 2 +- .../scala/tests/SemanticTokensLspSuite.scala | 14 ++-- 11 files changed, 106 insertions(+), 53 deletions(-) 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 3acfb5c2f51..97ed0363535 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -128,6 +128,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( @@ -257,6 +259,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. 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 b31d1ed2047..229711e0e1b 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -220,7 +220,7 @@ public CompletableFuture> info(String symbol) { /** * Clean the symbol table and other mutable state in the compiler. */ - public void restart(Boolean successfulCompilation) { + public void restart(boolean successfulCompilation) { restart(); } @@ -338,4 +338,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 2fd39580d77..a8a9ffbc6cf 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 @@ -67,7 +67,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] @@ -665,7 +666,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) => @@ -684,11 +686,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 1c8855635ea..625f9c85502 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 @@ -70,10 +70,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 @@ -150,13 +154,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 { @@ -171,26 +176,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() @@ -215,8 +219,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() @@ -225,9 +236,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) } @@ -285,7 +300,7 @@ case class ScalaPresentationCompiler( override def complete( params: OffsetParams - ): CompletableFuture[CompletionList] = + ): CompletableFuture[CompletionList] = { compilerAccess.withInterruptableCompiler(Some(params))( EmptyCompletionList(), params.token @@ -293,6 +308,7 @@ case class ScalaPresentationCompiler( new CompletionProvider(pc.compiler(outlineFiles(params)), params) .completions() } + } override def implementAbstractMembers( params: OffsetParams @@ -598,4 +614,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 ffd5c4d56d7..3344b54bd14 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 @@ -28,7 +28,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( From 61cd2e2ceede1699899694223b5dd8ad3aec895f Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 27 Feb 2024 15:33:19 +0100 Subject: [PATCH 08/20] restrict when presentation compiler is restarted --- .../meta/internal/metals/Compilers.scala | 1 + .../pc/ScalaPresentationCompiler.scala | 43 +++++++++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) 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 97ed0363535..c4dc31e075c 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -271,6 +271,7 @@ class Compilers( for { target <- buildTargets.allInverseDependencies(report.getTarget) if target != report.getTarget + if wasSuccessfullyCompiled.getOrElse(target, true) compiler <- buildTargetPCFromCache(target) } { compiler.restart() 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 625f9c85502..228c46dc709 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 @@ -75,8 +75,12 @@ case class ScalaPresentationCompiler( ) extends PresentationCompiler { private val wasSuccessfullyCompiled = - new util.concurrent.atomic.AtomicReference[Option[Boolean]]( - wasSuccessfullyCompiledInitial + new util.concurrent.atomic.AtomicReference[ + CompilationStatus.WasSuccessfullyCompiled + ]( + CompilationStatus.fromWasSuccessfullyCompiled( + wasSuccessfullyCompiledInitial + ) ) implicit val executionContext: ExecutionContextExecutor = ec @@ -155,16 +159,20 @@ case class ScalaPresentationCompiler( def restart(): Unit = restoreOutlineAndRestart() override def restart(wasSuccessful: Boolean): Unit = { - wasSuccessfullyCompiled.updateAndGet { - case None => Some(wasSuccessful) - case _ if wasSuccessful => Some(true) + val prevCompilationStatus = wasSuccessfullyCompiled.getAndUpdate { + case _ if wasSuccessful => + CompilationStatus.SuccessfullyCompiled + case CompilationStatus.WaitingForCompilationResult => + CompilationStatus.UnSuccessfullyCompiled case value => value } if (wasSuccessful) { changedDocuments.clear() compilerAccess.shutdownCurrentCompiler() - } else { + } else if ( + prevCompilationStatus == CompilationStatus.WaitingForCompilationResult + ) { restoreOutlineAndRestart() } } @@ -221,10 +229,10 @@ case class ScalaPresentationCompiler( ): OutlineFiles = { val shouldCompileAll = wasSuccessfullyCompiled .getAndUpdate { - case Some(false) => Some(true) + case CompilationStatus.UnSuccessfullyCompiled => + CompilationStatus.OutlinedByPC case value => value - } - .exists(!_) + } == CompilationStatus.UnSuccessfullyCompiled val result = if (shouldCompileAll) { @@ -622,3 +630,20 @@ case class ScalaPresentationCompiler( } } + +object CompilationStatus { + sealed trait WasSuccessfullyCompiled + case object SuccessfullyCompiled extends WasSuccessfullyCompiled + case object UnSuccessfullyCompiled extends WasSuccessfullyCompiled + case object WaitingForCompilationResult extends WasSuccessfullyCompiled + case object OutlinedByPC extends WasSuccessfullyCompiled + + def fromWasSuccessfullyCompiled( + wasSuccessfullyCompiled: Option[Boolean] + ): WasSuccessfullyCompiled = + wasSuccessfullyCompiled match { + case None => WaitingForCompilationResult + case Some(true) => SuccessfullyCompiled + case Some(false) => UnSuccessfullyCompiled + } +} From 466786d9519d560439f644188196f2bd94990c92 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 18 Mar 2024 13:50:30 +0100 Subject: [PATCH 09/20] test: add new test --- .../test/scala/tests/CompilersLspSuite.scala | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index 142f0476770..35544e9c787 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -286,4 +286,56 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { ) } yield () } + + test("never-compiling2") { + cleanWorkspace() + for { + _ <- initialize( + """/metals.json + |{ + | "a": {} + |} + |/a/src/main/scala/a/A.scala + |package a + |case class A(bar: String) + |object O { + | type T = A + |} + |/a/src/main/scala/b/B.scala + |package b + |import a.O.T + | + |object B { + | def getT: T = ??? + |} + |/a/src/main/scala/c/C.scala + |package c + |import b.B + | + |object C { + | val i: Int = "aaa" + | def foo = B.getT + | def bar = foo.bar + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/a/A.scala") + _ <- server.didOpen("a/src/main/scala/b/B.scala") + _ <- server.didOpen("a/src/main/scala/c/C.scala") + _ = assertNoDiff( + server.client.workspaceDiagnostics, + """|a/src/main/scala/c/C.scala:5:16: error: type mismatch; + | found : String("aaa") + | required: Int + | val i: Int = "aaa" + | ^^^^^ + |""".stripMargin + ) + _ <- assertCompletion( + " def bar = foo.bar@@", + "bar: String", + filename = Some("a/src/main/scala/c/C.scala") + ) + } yield () + } } From 73f961aaffaa39d0c8a3ab3a1d005fb935b5257c Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 19 Mar 2024 11:01:06 +0100 Subject: [PATCH 10/20] move logic outline files logic out of pc --- .../meta/internal/metals/Compilers.scala | 294 ++++++++++++------ .../metals/InteractiveSemanticdbs.scala | 104 ------- .../metals/OutlineFilesProvider.scala | 191 ++++++++++++ .../java/scala/meta/pc/CompilerFiles.java | 6 - .../main/java/scala/meta/pc/OutlineFiles.java | 16 + .../scala/meta/pc/PresentationCompiler.java | 26 +- .../java/scala/meta/pc/VirtualFileParams.java | 9 + .../metals/CompilerInlayHintsParams.scala | 4 + .../metals/CompilerOffsetParams.scala | 33 +- .../metals/CompilerVirtualFileParams.scala | 14 +- .../meta/internal/pc/CompilerWrapper.scala | 13 +- .../scala/meta/internal/pc/Compat.scala | 2 + .../scala/meta/internal/pc/Compat.scala | 12 +- .../meta/internal/pc/Compat.scala | 35 +++ .../meta/internal/pc/Compat.scala | 35 +++ .../meta/internal/pc/Compat.scala | 36 +++ .../meta/internal/pc/Compat.scala | 35 +++ .../scala/meta/internal/pc/Compat.scala | 10 +- .../scala/meta/internal/pc/MetalsGlobal.scala | 2 +- .../internal/pc/ScalaCompilerAccess.scala | 8 +- .../pc/ScalaPresentationCompiler.scala | 178 ++--------- .../internal/pc/Scala3CompilerWrapper.scala | 2 +- .../pc/ScalaPresentationCompiler.scala | 2 +- .../internal/metals/OffsetParamsUtils.scala | 30 +- .../test/scala/tests/CompilersLspSuite.scala | 4 +- .../codeactions/OrganizeImportsLspSuite.scala | 4 +- 26 files changed, 695 insertions(+), 410 deletions(-) create mode 100644 metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala delete mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java create mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/OutlineFiles.java 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 c4dc31e075c..e62b6fc9465 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -27,7 +27,6 @@ import scala.meta.internal.worksheets.WorksheetProvider import scala.meta.io.AbsolutePath import scala.meta.pc.AutoImportsResult import scala.meta.pc.CancelToken -import scala.meta.pc.CompilerFiles import scala.meta.pc.HoverSignature import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompiler @@ -114,6 +113,9 @@ class Compilers( } } + val plugins = new CompilerPlugins() + val outlineFilesProvider = new OutlineFilesProvider(buildTargets, buffers) + // Not a TrieMap because we want to avoid loading duplicate compilers for the same build target. // Not a `j.u.c.ConcurrentHashMap` because it can deadlock in `computeIfAbsent` when the absent // function is expensive, which is the case here. @@ -128,8 +130,6 @@ class Compilers( ) private val worksheetsDigests = new TrieMap[AbsolutePath, String]() - private val wasSuccessfullyCompiled = - new TrieMap[BuildTargetIdentifier, Boolean]() private val cache = jcache.asScala private def buildTargetPCFromCache( @@ -176,6 +176,7 @@ class Compilers( cache.clear() worksheetsCache.clear() worksheetsDigests.clear() + outlineFilesProvider.clear() } def restartAll(): Unit = { @@ -211,10 +212,9 @@ class Compilers( } def didClose(path: AbsolutePath): Unit = { - loadCompiler(path) - .map { pc => - pc.didClose(path.toNIO.toUri()) - } + loadCompiler(path).foreach { case (pc, _) => + pc.didClose(path.toNIO.toUri()) + } } def didChange(path: AbsolutePath): Future[List[Diagnostic]] = { @@ -223,7 +223,7 @@ class Compilers( .toInputFromBuffers(buffers) loadCompiler(path) - .map { pc => + .map { case (pc, id) => val inputAndAdjust = if ( path.isWorksheet && ScalaVersions.isScala3Version( @@ -240,6 +240,8 @@ class Compilers( AdjustedLspData.default, ) + id.foreach(outlineFilesProvider.didChange(_, path)) + for { ds <- pc @@ -257,13 +259,19 @@ class Compilers( def didCompile(report: CompileReport): Unit = { val isSuccessful = report.getErrors == 0 - buildTargetPCFromCache(report.getTarget).foreach(_.restart(isSuccessful)) - - wasSuccessfullyCompiled.updateWith(report.getTarget()) { - case Some(true) => Some(true) - case _ => Some(isSuccessful) + buildTargetPCFromCache(report.getTarget).foreach { pc => + if ( + outlineFilesProvider.shouldRestartPc( + report.getTarget, + DidCompile(isSuccessful), + ) + ) { + pc.restart() + } } + outlineFilesProvider.onDidCompile(report.getTarget(), isSuccessful) + if (isSuccessful) { // Restart PC for all build targets that depend on this target since the classfiles // may have changed. @@ -271,7 +279,7 @@ class Compilers( for { target <- buildTargets.allInverseDependencies(report.getTarget) if target != report.getTarget - if wasSuccessfullyCompiled.getOrElse(target, true) + if outlineFilesProvider.shouldRestartPc(target, InverseDependency) compiler <- buildTargetPCFromCache(target) } { compiler.restart() @@ -328,7 +336,7 @@ class Compilers( } } loadCompiler(path) - .map { compiler => + .map { case (compiler, buildTargetId) => val input = path.toInputFromBuffers(buffers) breakpointPosition.toMeta(input) match { case Some(metaPos) => @@ -363,12 +371,14 @@ class Compilers( if (isZeroBased) i else i + 1 } - val offsetParams = CompilerOffsetParams( - path.toURI, - modified, - rangeEnd, - token, - ) + val offsetParams = + CompilerOffsetParams( + path.toURI, + modified, + rangeEnd, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) val previousLines = expression .getText() @@ -422,7 +432,7 @@ class Compilers( } else { val path = params.getTextDocument.getUri.toAbsolutePath loadCompiler(path) - .map { compiler => + .map { case (compiler, buildTargetId) => val (input, _, adjust) = sourceAdjustments( params.getTextDocument().getUri(), @@ -523,7 +533,12 @@ class Compilers( } val vFile = - CompilerVirtualFileParams(path.toNIO.toUri(), input.text, token) + CompilerVirtualFileParams( + path.toNIO.toUri(), + input.text, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) val isScala3 = ScalaVersions.isScala3Version(compiler.scalaVersion()) compiler @@ -564,7 +579,7 @@ class Compilers( params: InlayHintParams, token: CancelToken, ): Future[ju.List[InlayHint]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => def inlayHintsFallback( params: SyntheticDecorationsParams ): Future[ju.List[InlayHint]] = { @@ -602,7 +617,11 @@ class Compilers( } val rangeParams = - CompilerRangeParamsUtils.fromPos(pos, token) + CompilerRangeParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) val options = userConfig().inlayHintsOptions val pcParams = CompilerInlayHintsParams( rangeParams, @@ -632,9 +651,10 @@ class Compilers( params: CompletionParams, token: CancelToken, ): Future[CompletionList] = - withPCAndAdjustLsp(params) { (pc, pos, adjust) => + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + val outlineFiles = outlineFilesProvider.getOutlineFiles(buildTargetId) val offsetParams = - CompilerOffsetParamsUtils.fromPos(pos, token) + CompilerOffsetParamsUtils.fromPos(pos, token, outlineFiles) pc.complete(offsetParams) .asScala .map { list => @@ -649,10 +669,14 @@ class Compilers( findExtensionMethods: Boolean, token: CancelToken, ): Future[ju.List[AutoImportsResult]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => pc.autoImports( name, - CompilerOffsetParamsUtils.fromPos(pos, token), + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ), findExtensionMethods, ).asScala .map { list => @@ -666,9 +690,14 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => - pc.insertInferredType(CompilerOffsetParamsUtils.fromPos(pos, token)) - .asScala + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + pc.insertInferredType( + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) + ).asScala .map { edits => adjust.adjustTextEdits(edits) } @@ -679,9 +708,14 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[TextEdit]] = - withPCAndAdjustLsp(params) { (pc, pos, adjust) => - pc.inlineValue(CompilerOffsetParamsUtils.fromPos(pos, token)) - .asScala + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + pc.inlineValue( + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) + ).asScala .map(adjust.adjustTextEdits) }.getOrElse(Future.successful(Nil.asJava)) @@ -689,9 +723,14 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[DocumentHighlight]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => - pc.documentHighlight(CompilerOffsetParamsUtils.fromPos(pos, token)) - .asScala + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + pc.documentHighlight( + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) + ).asScala .map { highlights => adjust.adjustDocumentHighlight(highlights) } @@ -705,9 +744,13 @@ class Compilers( token: CancelToken, ): Future[ju.List[TextEdit]] = { withPCAndAdjustLsp(doc.getUri(), range, extractionPos) { - (pc, metaRange, metaExtractionPos, adjust) => + (pc, metaRange, metaExtractionPos, adjust, buildTargetId) => pc.extractMethod( - CompilerRangeParamsUtils.fromPos(metaRange, token), + CompilerRangeParamsUtils.fromPos( + metaRange, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ), CompilerOffsetParamsUtils.fromPos(metaExtractionPos, token), ).asScala .map { edits => @@ -721,9 +764,13 @@ class Compilers( argIndices: ju.List[Integer], token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(position) { (pc, pos, adjust) => + withPCAndAdjustLsp(position) { (pc, pos, adjust, buildTargetId) => pc.convertToNamedArguments( - CompilerOffsetParamsUtils.fromPos(pos, token), + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ), argIndices, ).asScala .map { edits => @@ -736,9 +783,14 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => - pc.implementAbstractMembers(CompilerOffsetParamsUtils.fromPos(pos, token)) - .asScala + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + pc.implementAbstractMembers( + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) + ).asScala .map { edits => adjust.adjustTextEdits(edits) } @@ -749,9 +801,13 @@ class Compilers( params: HoverExtParams, token: CancelToken, ): Future[Option[HoverSignature]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => pc.hover( - CompilerRangeParamsUtils.offsetOrRange(pos, token) + CompilerRangeParamsUtils.offsetOrRange( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) ).asScala .map(_.asScala.map { hover => adjust.adjustHoverResp(hover) }) } @@ -761,9 +817,13 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.Optional[LspRange]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => pc.prepareRename( - CompilerRangeParamsUtils.offsetOrRange(pos, token) + CompilerRangeParamsUtils.offsetOrRange( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) ).asScala .map { range => range.map(adjust.adjustRange(_)) @@ -775,9 +835,13 @@ class Compilers( params: RenameParams, token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust) => + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => pc.rename( - CompilerRangeParamsUtils.offsetOrRange(pos, token), + CompilerRangeParamsUtils.offsetOrRange( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ), params.getNewName(), ).asScala .map { edits => @@ -805,10 +869,11 @@ class Compilers( symbol: String, ): Future[Option[PcSymbolInformation]] = { loadCompiler(path, forceScala = true) - .map( - _.info(symbol).asScala + .map { case (pc, _) => + pc.info(symbol) + .asScala .map(_.asScala.map(PcSymbolInformation.from)) - ) + } .getOrElse(Future(None)) } @@ -817,8 +882,12 @@ class Compilers( token: CancelToken, findTypeDef: Boolean, ): Future[DefinitionResult] = - withPCAndAdjustLsp(params) { (pc, pos, adjust) => - val params = CompilerOffsetParamsUtils.fromPos(pos, token) + withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + val params = CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) val defResult = if (findTypeDef) pc.typeDefinition(params) else @@ -853,17 +922,29 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[SignatureHelp] = - withPCAndAdjustLsp(params) { (pc, pos, _) => - pc.signatureHelp(CompilerOffsetParamsUtils.fromPos(pos, token)).asScala + withPCAndAdjustLsp(params) { (pc, pos, _, buildTargetId) => + pc.signatureHelp( + CompilerOffsetParamsUtils.fromPos( + pos, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) + ).asScala }.getOrElse(Future.successful(new SignatureHelp())) def selectionRange( params: SelectionRangeParams, token: CancelToken, ): Future[ju.List[SelectionRange]] = { - withPCAndAdjustLsp(params) { (pc, positions) => + withPCAndAdjustLsp(params) { (pc, positions, buildTargetId) => val offsetPositions: ju.List[OffsetParams] = - positions.map(CompilerOffsetParamsUtils.fromPos(_, token)) + positions.map( + CompilerOffsetParamsUtils.fromPos( + _, + token, + outlineFilesProvider.getOutlineFiles(buildTargetId), + ) + ) pc.selectionRange(offsetPositions).asScala }.getOrElse(Future.successful(Nil.asJava)) } @@ -890,34 +971,29 @@ class Compilers( def loadCompiler( path: AbsolutePath, forceScala: Boolean = false, - ): Option[PresentationCompiler] = { + ): Option[(PresentationCompiler, Option[BuildTargetIdentifier])] = { - def fromBuildTarget: Option[PresentationCompiler] = { + def fromBuildTarget: Option[(PresentationCompiler, Option[BuildTargetIdentifier])] = { val target = buildTargets .inverseSources(path) target match { - case None => - val tmpDirectory = workspace.resolve(Directories.tmp) - val scalaVersion = - scalaVersionSelector.fallbackScalaVersion(isAmmonite = false) - if (!path.toNIO.startsWith(tmpDirectory.toNIO)) - scribe.info( - s"no build target found for $path. Using presentation compiler with project's scala-library version: ${scalaVersion}" - ) - Some(fallbackCompiler) + case None => Some((fallbackCompiler(path), None)) case Some(value) => - if (path.isScalaFilename) loadCompiler(value) + if (path.isScalaFilename) loadCompiler(value).map((_, Some(value))) else if (path.isJavaFilename && forceScala) - loadCompiler(value).orElse(Some(loadJavaCompiler(value))) - else if (path.isJavaFilename) Some(loadJavaCompiler(value)) + loadCompiler(value) + .orElse(loadJavaCompiler(value)) + .map((_, Some(value))) + else if (path.isJavaFilename) + loadJavaCompiler(value).map((_, Some(value))) else None } } if (!path.isScalaFilename && !path.isJavaFilename) None else if (path.isWorksheet) - loadWorksheetCompiler(path).orElse(fromBuildTarget) + loadWorksheetCompiler(path).map((_, None)).orElse(fromBuildTarget) else fromBuildTarget } @@ -1043,9 +1119,15 @@ class Compilers( private def withPCAndAdjustLsp[T]( params: SelectionRangeParams - )(fn: (PresentationCompiler, ju.List[Position]) => T): Option[T] = { + )( + fn: ( + PresentationCompiler, + ju.List[Position], + Option[BuildTargetIdentifier], + ) => T + ): Option[T] = { val path = params.getTextDocument.getUri.toAbsolutePath - loadCompiler(path).map { compiler => + loadCompiler(path).map { case (compiler, buildTargetId) => val input = path .toInputFromBuffers(buffers) .copy(path = params.getTextDocument.getUri) @@ -1053,35 +1135,53 @@ class Compilers( val positions = params.getPositions().asScala.flatMap(_.toMeta(input)).asJava - fn(compiler, positions) + fn(compiler, positions, buildTargetId) } } private def withPCAndAdjustLsp[T]( params: TextDocumentPositionParams - )(fn: (PresentationCompiler, Position, AdjustLspData) => T): Option[T] = { + )( + fn: ( + PresentationCompiler, + Position, + AdjustLspData, + Option[BuildTargetIdentifier], + ) => T + ): Option[T] = { val path = params.getTextDocument.getUri.toAbsolutePath - loadCompiler(path).flatMap { compiler => + loadCompiler(path).flatMap { case (compiler, buildTargetId) => val (input, pos, adjust) = sourceAdjustments( params, compiler.scalaVersion(), ) - pos.toMeta(input).map(metaPos => fn(compiler, metaPos, adjust)) + pos + .toMeta(input) + .map(metaPos => fn(compiler, metaPos, adjust, buildTargetId)) } } private def withPCAndAdjustLsp[T]( params: InlayHintParams - )(fn: (PresentationCompiler, Position, AdjustLspData) => T): Option[T] = { + )( + fn: ( + PresentationCompiler, + Position, + AdjustLspData, + Option[BuildTargetIdentifier], + ) => T + ): Option[T] = { val path = params.getTextDocument.getUri.toAbsolutePath - loadCompiler(path).flatMap { compiler => + loadCompiler(path).flatMap { case (compiler, buildTargetId) => val (input, pos, adjust) = sourceAdjustments( params, compiler.scalaVersion(), ) - pos.toMeta(input).map(metaPos => fn(compiler, metaPos, adjust)) + pos + .toMeta(input) + .map(metaPos => fn(compiler, metaPos, adjust, buildTargetId)) } } @@ -1095,10 +1195,11 @@ class Compilers( Position, Position, AdjustLspData, + Option[BuildTargetIdentifier], ) => T ): Option[T] = { val path = uri.toAbsolutePath - loadCompiler(path).flatMap { compiler => + loadCompiler(path).flatMap { case (compiler, buildTargetId) => val (input, adjustRequest, adjustResponse) = sourceAdjustments( uri, @@ -1110,22 +1211,35 @@ class Compilers( adjustRequest(range.getEnd()), ).toMeta(input) metaExtractionPos <- adjustRequest(extractionPos).toMeta(input) - } yield fn(compiler, metaRange, metaExtractionPos, adjustResponse) + } yield fn( + compiler, + metaRange, + metaExtractionPos, + adjustResponse, + buildTargetId, + ) } } private def withPCAndAdjustLsp[T]( params: HoverExtParams - )(fn: (PresentationCompiler, Position, AdjustLspData) => T): Option[T] = { + )( + fn: ( + PresentationCompiler, + Position, + AdjustLspData, + Option[BuildTargetIdentifier], + ) => T + ): Option[T] = { val path = params.textDocument.getUri.toAbsolutePath - loadCompiler(path).flatMap { compiler => + loadCompiler(path).flatMap { case (compiler, buildTargetId) => if (params.range != null) { val (input, range, adjust) = sourceAdjustments( params, compiler.scalaVersion(), ) - range.toMeta(input).map(fn(compiler, _, adjust)) + range.toMeta(input).map(fn(compiler, _, adjust, buildTargetId)) } else { val positionParams = @@ -1137,7 +1251,7 @@ class Compilers( positionParams, compiler.scalaVersion(), ) - pos.toMeta(input).map(fn(compiler, _, adjust)) + pos.toMeta(input).map(fn(compiler, _, adjust, buildTargetId)) } } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala index 247a6ff14d0..619a0698701 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala @@ -148,108 +148,4 @@ final class InteractiveSemanticdbs( else scalaCompile(source, text) } - private def scalaCompile( - source: AbsolutePath, - text: String, - ): s.TextDocument = { - - val pc = compilers() - .loadCompiler(source) - .orElse { - // load presentation compiler for sources that were create by a worksheet definition request - tables.worksheetSources - .getWorksheet(source) - .flatMap(compilers().loadWorksheetCompiler) - } - .getOrElse { - // this is highly unlikely since None is only returned for non scala/java files - throw new RuntimeException( - s"No presentation compiler found for $source" - ) - } - - val (prependedLinesSize, modifiedText) = - Option - .when(source.isSbt)( - buildTargets - .sbtAutoImports(source) - ) - .flatten - .fold((0, text))(imports => - (imports.size, SbtBuildTool.prependAutoImports(text, imports)) - ) - - // NOTE(olafur): it's unfortunate that we block on `semanticdbTextDocument` - // here but to avoid it we would need to refactor the `Semanticdbs` trait, - // which requires more effort than it's worth. - val bytes = Await - .result( - pc.semanticdbTextDocument(source.toURI, modifiedText).asScala, - Duration( - clientConfig.initialConfig.compilers.timeoutDelay, - clientConfig.initialConfig.compilers.timeoutUnit, - ), - ) - val textDocument = { - val doc = s.TextDocument.parseFrom(bytes) - if (doc.text.isEmpty()) doc.withText(text) - else doc - } - if (prependedLinesSize > 0) - cleanupAutoImports(textDocument, text, prependedLinesSize) - else textDocument - } - - private def cleanupAutoImports( - document: s.TextDocument, - originalText: String, - linesSize: Int, - ): s.TextDocument = { - - def adjustRange(range: s.Range): Option[s.Range] = { - val nextStartLine = range.startLine - linesSize - val nextEndLine = range.endLine - linesSize - if (nextEndLine >= 0) { - val nextRange = range.copy( - startLine = nextStartLine, - endLine = nextEndLine, - ) - Some(nextRange) - } else None - } - - val adjustedOccurences = - document.occurrences.flatMap { occurence => - occurence.range - .flatMap(adjustRange) - .map(r => occurence.copy(range = Some(r))) - } - - val adjustedDiagnostic = - document.diagnostics.flatMap { diagnostic => - diagnostic.range - .flatMap(adjustRange) - .map(r => diagnostic.copy(range = Some(r))) - } - - val adjustedSynthetic = - document.synthetics.flatMap { synthetic => - synthetic.range - .flatMap(adjustRange) - .map(r => synthetic.copy(range = Some(r))) - } - - s.TextDocument( - schema = document.schema, - uri = document.uri, - text = originalText, - md5 = MD5.compute(originalText), - language = document.language, - symbols = document.symbols, - occurrences = adjustedOccurences, - diagnostics = adjustedDiagnostic, - synthetics = adjustedSynthetic, - ) - } - } diff --git a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala new file mode 100644 index 00000000000..e9ec7cdec5a --- /dev/null +++ b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala @@ -0,0 +1,191 @@ +package scala.meta.internal.metals + +import java.util.Optional +import java.util.concurrent.atomic.AtomicBoolean +import java.{util => ju} + +import scala.collection.concurrent.TrieMap + +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.io.AbsolutePath +import scala.meta.pc.VirtualFileParams +import scala.meta.pc.{OutlineFiles => JOutlineFiles} + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier + +class OutlineFilesProvider( + buildTargets: BuildTargets, + buffers: Buffers, +) { + private val outlineFiles = + new TrieMap[BuildTargetIdentifier, BuildTargetOutlineFilesProvider]() + + def shouldRestartPc( + id: BuildTargetIdentifier, + reason: PcRestartReason, + ): Boolean = { + reason match { + case DidCompile(true) => true + case _ => + outlineFiles.get(id) match { + case Some(provider) => + // if it was never compiled successfully by the build server + // we don't restart pc not to lose information from outline compile + provider.wasSuccessfullyCompiledByBuildServer + case None => true + } + } + } + + def onDidCompile(id: BuildTargetIdentifier, wasSuccessful: Boolean): Unit = { + outlineFiles.get(id) match { + case Some(provider) => + if (wasSuccessful) provider.successfulCompilation() + case None => + for { + scalaTarget <- buildTargets.scalaTarget(id) + // we don't perform outline compilation for Scala 3 + if (!ScalaVersions.isScala3Version(scalaTarget.scalaVersion)) + } outlineFiles.putIfAbsent( + id, + new BuildTargetOutlineFilesProvider( + buildTargets, + buffers, + id, + wasSuccessful, + ), + ) + } + } + + def didChange(id: BuildTargetIdentifier, path: AbsolutePath): Unit = { + for { + provider <- outlineFiles.get(id) + } provider.didChange(path) + } + + def getOutlineFiles( + buildTargetId: Option[BuildTargetIdentifier] + ): Optional[JOutlineFiles] = { + val res: Option[JOutlineFiles] = + for { + id <- buildTargetId + provider <- outlineFiles.get(id) + outlineFiles <- provider.outlineFiles() + } yield outlineFiles + res.asJava + } + + def enrichWithOutlineFiles( + buildTargetId: Option[BuildTargetIdentifier] + )(vFile: CompilerVirtualFileParams): CompilerVirtualFileParams = { + val optOutlineFiles = + for { + id <- buildTargetId + provider <- outlineFiles.get(id) + outlineFiles <- provider.outlineFiles() + } yield outlineFiles + + optOutlineFiles + .map(outlineFiles => vFile.copy(outlineFiles = Optional.of(outlineFiles))) + .getOrElse(vFile) + } + + def enrichWithOutlineFiles( + path: AbsolutePath + )(vFile: CompilerVirtualFileParams): CompilerVirtualFileParams = { + val optOutlineFiles = + for { + bt <- buildTargets.inferBuildTarget(path) + provider <- outlineFiles.get(bt) + outlineFiles <- provider.outlineFiles() + } yield outlineFiles + + optOutlineFiles + .map(outlineFiles => vFile.copy(outlineFiles = Optional.of(outlineFiles))) + .getOrElse(vFile) + } + + def clear(): Unit = { + outlineFiles.clear() + } +} + +class BuildTargetOutlineFilesProvider( + buildTargets: BuildTargets, + buffers: Buffers, + id: BuildTargetIdentifier, + wasCompilationSuccessful: Boolean, +) { + private val changedDocuments = + ConcurrentHashSet.empty[AbsolutePath] + + private val wasAllOutlined: AtomicBoolean = + new AtomicBoolean(false) + + private val wasSuccessfullyCompiled: AtomicBoolean = + new AtomicBoolean(wasCompilationSuccessful) + + def wasSuccessfullyCompiledByBuildServer: Boolean = + wasSuccessfullyCompiled.get() + + def successfulCompilation(): Unit = { + wasSuccessfullyCompiled.set(true) + changedDocuments.clear() + } + + def didChange(path: AbsolutePath): Boolean = + changedDocuments.add(path) + + def outlineFiles(): Option[OutlineFiles] = { + if (!wasSuccessfullyCompiled.get() && !wasAllOutlined.getAndSet(true)) { + // initial outline compilation that is a substitute for build server compilation + val allFiles = + buildTargets + .buildTargetSources(id) + .flatMap(_.listRecursive.toList) + .flatMap(toVirtualFileParams(_)) + .toList + .asJava + Some( + OutlineFiles( + allFiles, + isFirstCompileSubstitute = true, + ) + ) + } else { + changedDocuments.asScala.toList.flatMap( + toVirtualFileParams + ) match { + case Nil => None + case files => + Some( + OutlineFiles( + files.asJava, + isFirstCompileSubstitute = false, + ) + ) + } + } + } + + private def toVirtualFileParams( + path: AbsolutePath + ): Option[VirtualFileParams] = + buffers.get(path).orElse(path.readTextOpt).map { text => + CompilerVirtualFileParams( + path.toURI, + text, + ) + } + +} + +sealed trait PcRestartReason +case object InverseDependency extends PcRestartReason +case class DidCompile(wasSuccess: Boolean) extends PcRestartReason + +case class OutlineFiles( + files: ju.List[VirtualFileParams], + isFirstCompileSubstitute: Boolean = false, +) extends JOutlineFiles diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java b/mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java deleted file mode 100644 index 68adb2acfed..00000000000 --- a/mtags-interfaces/src/main/java/scala/meta/pc/CompilerFiles.java +++ /dev/null @@ -1,6 +0,0 @@ -package scala.meta.pc; - -public interface CompilerFiles { - - java.util.List allPaths(); -} diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/OutlineFiles.java b/mtags-interfaces/src/main/java/scala/meta/pc/OutlineFiles.java new file mode 100644 index 00000000000..28cd919872b --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/OutlineFiles.java @@ -0,0 +1,16 @@ +package scala.meta.pc; +import java.util.List; + +public interface OutlineFiles { + + /** + * Will this outline compilation be substitute for build server's compilation. + * Used if the first compilation using build server is unsuccessful. + */ + boolean isFirstCompileSubstitute(); + + /** + * Files that should be outline compiled before calculating result. + */ + List files(); +} 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 229711e0e1b..5ffde6d79fd 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -196,6 +196,14 @@ public CompletableFuture> info(String symbol) { */ public abstract CompletableFuture semanticdbTextDocument(URI filename, String code); + /** + * Returns the Protobuf byte array representation of a SemanticDB + * TextDocument for the given source. + */ + public CompletableFuture semanticdbTextDocument(VirtualFileParams params) { + return semanticdbTextDocument(params.uri(), params.text()); + } + /** * Return the selections ranges for the given positions. */ @@ -217,13 +225,6 @@ public CompletableFuture> info(String symbol) { */ public abstract void shutdown(); - /** - * Clean the symbol table and other mutable state in the compiler. - */ - public void restart(boolean successfulCompilation) { - restart(); - } - /** * Clean the symbol table and other mutable state in the compiler. */ @@ -243,13 +244,6 @@ public PresentationCompiler withBuildTargetName(String buildTargetName) { return this; }; - /** - * Set an ability to search for all files within the compiler - */ - public PresentationCompiler withCompilerFiles(CompilerFiles files) { - return this; - }; - /** * Provide a SymbolSearch to extract docstrings, java parameter names and Scala * default parameter values. @@ -338,8 +332,4 @@ public abstract PresentationCompiler newInstance(String buildTargetIdentifier, L */ public abstract String scalaVersion(); - public PresentationCompiler withWasSuccessfullyCompiled(boolean wasSuccessful) { - return this; - } - } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/VirtualFileParams.java b/mtags-interfaces/src/main/java/scala/meta/pc/VirtualFileParams.java index 904cb7da0fd..5a534e82203 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/VirtualFileParams.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/VirtualFileParams.java @@ -1,6 +1,7 @@ package scala.meta.pc; import java.net.URI; +import java.util.Optional; /** * Parameters for a presentation compiler request at a given offset in a single source file. @@ -21,6 +22,14 @@ public interface VirtualFileParams { */ CancelToken token(); + /** + * Information about files that changed since last compilation + * and should be outline compiled. + */ + default Optional outlineFiles() { + return Optional.empty(); + } + default void checkCanceled() { token().checkCanceled(); } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala index cebaf448c70..5d0e4246ca8 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala @@ -4,6 +4,8 @@ import java.net.URI import scala.meta.pc.CancelToken import scala.meta.pc.InlayHintsParams +import java.util.Optional +import scala.meta.pc.OutlineFiles case class CompilerInlayHintsParams( rangeParams: CompilerRangeParams, @@ -28,4 +30,6 @@ case class CompilerInlayHintsParams( implicitParameters = implicitParameters ) } + + override def outlineFiles(): Optional[OutlineFiles] = rangeParams.outlineFiles } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerOffsetParams.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerOffsetParams.scala index 71b3223d226..7bdf4b34558 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerOffsetParams.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerOffsetParams.scala @@ -1,30 +1,57 @@ package scala.meta.internal.metals import java.net.URI +import java.util.Optional import scala.meta.pc.CancelToken import scala.meta.pc.OffsetParams +import scala.meta.pc.OutlineFiles import scala.meta.pc.RangeParams case class CompilerOffsetParams( uri: URI, text: String, offset: Int, - token: CancelToken = EmptyCancelToken + token: CancelToken, + override val outlineFiles: Optional[OutlineFiles] ) extends OffsetParams +object CompilerOffsetParams { + def apply( + uri: URI, + text: String, + offset: Int, + token: CancelToken = EmptyCancelToken + ): CompilerOffsetParams = + CompilerOffsetParams(uri, text, offset, token, Optional.empty()) +} + case class CompilerRangeParams( uri: URI, text: String, offset: Int, endOffset: Int, - token: CancelToken = EmptyCancelToken + token: CancelToken, + override val outlineFiles: Optional[OutlineFiles] ) extends RangeParams { + def toCompilerOffsetParams: CompilerOffsetParams = CompilerOffsetParams( uri, text, offset, - token + token, + outlineFiles ) } + +object CompilerRangeParams { + def apply( + uri: URI, + text: String, + offset: Int, + endOffset: Int, + token: CancelToken = EmptyCancelToken + ): CompilerRangeParams = + CompilerRangeParams(uri, text, offset, endOffset, token, Optional.empty()) +} diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerVirtualFileParams.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerVirtualFileParams.scala index 954d3b0514a..ea4512f40e3 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerVirtualFileParams.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerVirtualFileParams.scala @@ -1,12 +1,24 @@ package scala.meta.internal.metals import java.net.URI +import java.util.Optional import scala.meta.pc.CancelToken +import scala.meta.pc.OutlineFiles import scala.meta.pc.VirtualFileParams case class CompilerVirtualFileParams( uri: URI, text: String, - token: CancelToken = EmptyCancelToken + token: CancelToken, + override val outlineFiles: Optional[OutlineFiles] ) extends VirtualFileParams + +object CompilerVirtualFileParams { + def apply( + uri: URI, + text: String, + token: CancelToken = EmptyCancelToken + ): CompilerVirtualFileParams = + CompilerVirtualFileParams(uri, text, token, Optional.empty()) +} 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 8836721b802..04246adf40e 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 @@ -14,19 +14,10 @@ trait CompilerWrapper[Reporter, Compiler] { def stop(): Unit - def compiler(changeFiles: OutlineFiles): Compiler + def compiler(params: VirtualFileParams): Compiler = compiler() - def compiler(): Compiler = compiler(OutlineFiles.empty) + def compiler(): Compiler def presentationCompilerThread: Option[Thread] } - -case class OutlineFiles( - files: List[VirtualFileParams], - firstCompileSubstitute: Boolean = false -) - -object OutlineFiles { - 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 8b77efec49d..e4e177109fc 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 @@ -3,6 +3,8 @@ package scala.meta.internal.pc import scala.tools.nsc.reporters.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.pc.OutlineFiles + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = { val dealiased = tpe.dealiasWiden 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 cc1c8c51a6f..8532ab9e00d 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 @@ -1,8 +1,12 @@ package scala.meta.internal.pc -import scala.tools.nsc.reporters.Reporter +import java.{util => ju} + +import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles import scala.meta.pc.VirtualFileParams trait Compat { this: MetalsGlobal => @@ -22,7 +26,7 @@ trait Compat { this: MetalsGlobal => def runOutline(files: OutlineFiles): Unit = { this.settings.Youtline.value = true runOutline(files.files) - if (files.firstCompileSubstitute) { + if (files.isFirstCompileSubstitute()) { // 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 @@ -32,10 +36,10 @@ trait Compat { this: MetalsGlobal => } private def runOutline( - files: List[VirtualFileParams], + files: ju.List[VirtualFileParams], forceNewUnit: Boolean = false ): Unit = { - files.foreach { params => + files.asScala.foreach { params => val unit = this.addCompilationUnit( params.text(), params.uri.toString(), diff --git a/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala index 988bd8f2db8..5f2527b504d 100644 --- a/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -19,4 +25,33 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } diff --git a/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala index 988bd8f2db8..5f2527b504d 100644 --- a/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -19,4 +25,33 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } diff --git a/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala index 988bd8f2db8..1d941c7ebc2 100644 --- a/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -19,4 +25,34 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } + } diff --git a/mtags/src/main/scala-2.13.8/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.8/meta/internal/pc/Compat.scala index 988bd8f2db8..5f2527b504d 100644 --- a/mtags/src/main/scala-2.13.8/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.8/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -19,4 +25,33 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } 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 19e8ec15b9c..0b4fe757b10 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 @@ -1,8 +1,12 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles import scala.meta.pc.VirtualFileParams trait Compat { this: MetalsGlobal => @@ -29,7 +33,7 @@ trait Compat { this: MetalsGlobal => def runOutline(files: OutlineFiles): Unit = { this.settings.Youtline.value = true runOutline(files.files) - if (files.firstCompileSubstitute) { + if (files.isFirstCompileSubstitute()) { // 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 @@ -39,10 +43,10 @@ trait Compat { this: MetalsGlobal => } private def runOutline( - files: List[VirtualFileParams], + files: ju.List[VirtualFileParams], forceNewUnit: Boolean = false ): Unit = { - files.foreach { params => + files.asScala.foreach { params => val unit = this.addCompilationUnit( params.text(), params.uri.toString(), 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 a8a9ffbc6cf..f8f598dbb82 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 @@ -70,7 +70,7 @@ class MetalsGlobal( val richCompilationCache: TrieMap[String, RichCompilationUnit] = TrieMap.empty[String, RichCompilationUnit] - // for those paths units were + // for those paths units were fully compiled (not just outlined) val fullyCompiled: mutable.Set[String] = mutable.Set.empty[String] class MetalsInteractiveAnalyzer(val global: compiler.type) 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 3ad5ab0d4b7..4184176b534 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,14 +9,18 @@ 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] { - override def compiler(files: OutlineFiles): MetalsGlobal = { - global.runOutline(files) + override def compiler(params: VirtualFileParams): MetalsGlobal = { + if (params.outlineFiles().isPresent()) { + global.runOutline(params.outlineFiles().get()) + } global } + override def compiler(): MetalsGlobal = global override def resetReporter(): Unit = global.reporter.reset() 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 228c46dc709..3f00608c40e 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 @@ -2,7 +2,6 @@ package scala.meta.internal.pc import java.io.File import java.net.URI -import java.nio.file.Files import java.nio.file.Path import java.util import java.util.Optional @@ -13,27 +12,22 @@ import java.util.logging.Logger import java.{util => ju} import scala.collection.Seq -import scala.collection.concurrent.TrieMap import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContextExecutor import scala.reflect.io.VirtualDirectory import scala.tools.nsc.Settings import scala.tools.nsc.reporters.StoreReporter -import scala.util.control.NonFatal import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerVirtualFileParams import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.EmptyReportContext -import scala.meta.internal.metals.Report import scala.meta.internal.metals.ReportContext import scala.meta.internal.metals.ReportLevel import scala.meta.internal.metals.StdReportContext import scala.meta.internal.mtags.BuildInfo import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.pc.AutoImportsResult -import scala.meta.pc.CompilerFiles import scala.meta.pc.DefinitionResult import scala.meta.pc.DisplayableException import scala.meta.pc.HoverSignature @@ -44,8 +38,6 @@ import scala.meta.pc.PresentationCompiler import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.RangeParams import scala.meta.pc.SymbolSearch -import scala.meta.pc.SyntheticDecoration -import scala.meta.pc.SyntheticDecorationsParams import scala.meta.pc.VirtualFileParams import scala.meta.pc.{PcSymbolInformation => IPcSymbolInformation} @@ -69,25 +61,13 @@ case class ScalaPresentationCompiler( sh: Option[ScheduledExecutorService] = None, config: PresentationCompilerConfig = PresentationCompilerConfigImpl(), folderPath: Option[Path] = None, - reportsLevel: ReportLevel = ReportLevel.Info, - compilerFiles: Option[CompilerFiles] = None, - wasSuccessfullyCompiledInitial: Option[Boolean] = None + reportsLevel: ReportLevel = ReportLevel.Info ) extends PresentationCompiler { - private val wasSuccessfullyCompiled = - new util.concurrent.atomic.AtomicReference[ - CompilationStatus.WasSuccessfullyCompiled - ]( - CompilationStatus.fromWasSuccessfullyCompiled( - wasSuccessfullyCompiledInitial - ) - ) - implicit val executionContext: ExecutionContextExecutor = ec val scalaVersion = BuildInfo.scalaCompilerVersion - private val changedDocuments = TrieMap.empty[URI, VirtualFileParams] val logger: Logger = Logger.getLogger(classOf[ScalaPresentationCompiler].getName) @@ -120,11 +100,6 @@ case class ScalaPresentationCompiler( ): PresentationCompiler = copy(sh = Some(sh)) - override def withCompilerFiles( - compilerFiles: CompilerFiles - ): PresentationCompiler = - copy(compilerFiles = Some(compilerFiles)) - override def withConfiguration( config: PresentationCompilerConfig ): PresentationCompiler = @@ -156,57 +131,7 @@ case class ScalaPresentationCompiler( compilerAccess.shutdown() } - def restart(): Unit = restoreOutlineAndRestart() - - override def restart(wasSuccessful: Boolean): Unit = { - val prevCompilationStatus = wasSuccessfullyCompiled.getAndUpdate { - case _ if wasSuccessful => - CompilationStatus.SuccessfullyCompiled - case CompilationStatus.WaitingForCompilationResult => - CompilationStatus.UnSuccessfullyCompiled - case value => value - } - - if (wasSuccessful) { - changedDocuments.clear() - compilerAccess.shutdownCurrentCompiler() - } else if ( - prevCompilationStatus == CompilationStatus.WaitingForCompilationResult - ) { - restoreOutlineAndRestart() - } - } - - private def restoreOutlineAndRestart() = { - compilerAccess - .withNonInterruptableCompiler(None)( - (), - 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 - ) - ) - logger - .log(util.logging.Level.SEVERE, error.getMessage(), error) - } - } - } - .get() - + def restart(): Unit = { compilerAccess.shutdownCurrentCompiler() } @@ -224,45 +149,9 @@ case class ScalaPresentationCompiler( ) } - private def outlineFiles( - current: VirtualFileParams - ): OutlineFiles = { - val shouldCompileAll = wasSuccessfullyCompiled - .getAndUpdate { - case CompilationStatus.UnSuccessfullyCompiled => - CompilationStatus.OutlinedByPC - case value => value - } == CompilationStatus.UnSuccessfullyCompiled - - val result = - if (shouldCompileAll) { - // if first compilation was unsuccessful we want to outline all files - compilerFiles.iterator.flatMap(_.allPaths().asScala).foreach { path => - val uri = path.toUri() - if (!changedDocuments.contains(uri)) { - val text = Files.readString(path) - changedDocuments += uri -> CompilerOffsetParams(uri, text, 0) - } - } - OutlineFiles( - changedDocuments.values.toList, - firstCompileSubstitute = true - ) - } else { - val files = - changedDocuments.values.filterNot(_.uri() == current.uri()).toList - OutlineFiles(files) - } - - changedDocuments.clear() - changedDocuments += current.uri() -> current - result - } - override def didChange( params: VirtualFileParams ): CompletableFuture[ju.List[Diagnostic]] = { - changedDocuments += params.uri() -> params CompletableFuture.completedFuture(Nil.asJava) } @@ -284,7 +173,7 @@ case class ScalaPresentationCompiler( params.token ) { pc => new PcSemanticTokensProvider( - pc.compiler(outlineFiles(params)), + pc.compiler(params), params ).provide().asJava } @@ -313,7 +202,7 @@ case class ScalaPresentationCompiler( EmptyCompletionList(), params.token ) { pc => - new CompletionProvider(pc.compiler(outlineFiles(params)), params) + new CompletionProvider(pc.compiler(params), params) .completions() } } @@ -326,7 +215,7 @@ case class ScalaPresentationCompiler( empty, params.token ) { pc => - new CompletionProvider(pc.compiler(outlineFiles(params)), params) + new CompletionProvider(pc.compiler(params), params) .implementAll() } } @@ -339,7 +228,7 @@ case class ScalaPresentationCompiler( empty, params.token ) { pc => - new InferredTypeProvider(pc.compiler(outlineFiles(params)), params) + new InferredTypeProvider(pc.compiler(params), params) .inferredTypeEdits() .asJava } @@ -352,7 +241,7 @@ case class ScalaPresentationCompiler( (compilerAccess .withInterruptableCompiler(Some(params))(empty, params.token) { pc => new PcInlineValueProviderImpl( - pc.compiler(outlineFiles(params)), + pc.compiler(params), params ).getInlineTextEdits }) @@ -370,7 +259,7 @@ case class ScalaPresentationCompiler( compilerAccess.withInterruptableCompiler(Some(range))(empty, range.token) { pc => new ExtractMethodProvider( - pc.compiler(outlineFiles(range)), + pc.compiler(range), range, extractionPos ).extractMethod.asJava @@ -385,7 +274,7 @@ case class ScalaPresentationCompiler( (compilerAccess .withInterruptableCompiler(Some(params))(empty, params.token) { pc => new ConvertToNamedArgumentsProvider( - pc.compiler(outlineFiles(params)), + pc.compiler(params), params, argIndices.asScala.map(_.toInt).toSet ).convertToNamedArguments @@ -405,7 +294,7 @@ case class ScalaPresentationCompiler( List.empty[AutoImportsResult].asJava, params.token ) { pc => - new AutoImportsProvider(pc.compiler(outlineFiles(params)), name, params) + new AutoImportsProvider(pc.compiler(params), name, params) .autoImports() .asJava } @@ -437,7 +326,7 @@ case class ScalaPresentationCompiler( new SignatureHelp(), params.token ) { pc => - new SignatureHelpProvider(pc.compiler(outlineFiles(params))) + new SignatureHelpProvider(pc.compiler(params)) .signatureHelp(params) } @@ -448,7 +337,7 @@ case class ScalaPresentationCompiler( Optional.empty[Range](), params.token ) { pc => - new PcRenameProvider(pc.compiler(outlineFiles(params)), params, None) + new PcRenameProvider(pc.compiler(params), params, None) .prepareRename() .asJava } @@ -462,7 +351,7 @@ case class ScalaPresentationCompiler( params.token ) { pc => new PcRenameProvider( - pc.compiler(outlineFiles(params)), + pc.compiler(params), params, Some(name) ).rename().asJava @@ -476,7 +365,7 @@ case class ScalaPresentationCompiler( params.token ) { pc => Optional.ofNullable( - new HoverProvider(pc.compiler(outlineFiles(params)), params, config.hoverContentType()) + new HoverProvider(pc.compiler(params), params, config.hoverContentType()) .hover() .orNull ) @@ -488,7 +377,7 @@ case class ScalaPresentationCompiler( DefinitionResultImpl.empty, params.token ) { pc => - new PcDefinitionProvider(pc.compiler(outlineFiles(params)), params) + new PcDefinitionProvider(pc.compiler(params), params) .definition() } } @@ -515,7 +404,7 @@ case class ScalaPresentationCompiler( DefinitionResultImpl.empty, params.token ) { pc => - new PcDefinitionProvider(pc.compiler(outlineFiles(params)), params) + new PcDefinitionProvider(pc.compiler(params), params) .typeDefinition() } } @@ -527,7 +416,7 @@ case class ScalaPresentationCompiler( List.empty[DocumentHighlight].asJava, params.token() ) { pc => - new PcDocumentHighlightProvider(pc.compiler(outlineFiles(params)), params) + new PcDocumentHighlightProvider(pc.compiler(params), params) .highlights() .asJava } @@ -537,15 +426,21 @@ case class ScalaPresentationCompiler( code: String ): CompletableFuture[Array[Byte]] = { val virtualFile = CompilerVirtualFileParams(fileUri, code) + semanticdbTextDocument(virtualFile) + } + + override def semanticdbTextDocument( + virtualFile: VirtualFileParams + ): CompletableFuture[Array[Byte]] = { compilerAccess.withInterruptableCompiler(Some(virtualFile))( Array.emptyByteArray, EmptyCancelToken ) { pc => new SemanticdbTextDocumentProvider( - pc.compiler(outlineFiles(CompilerOffsetParams(fileUri, code, 0))), + pc.compiler(virtualFile), config.semanticdbCompilerOptions().asScala.toList ) - .textDocument(fileUri, code) + .textDocument(virtualFile.uri(), virtualFile.text()) .toByteArray } } @@ -623,27 +518,4 @@ case class ScalaPresentationCompiler( .asJava } - override def withWasSuccessfullyCompiled( - wasSuccessful: Boolean - ): PresentationCompiler = { - this.copy(wasSuccessfullyCompiledInitial = Some(wasSuccessful)) - } - -} - -object CompilationStatus { - sealed trait WasSuccessfullyCompiled - case object SuccessfullyCompiled extends WasSuccessfullyCompiled - case object UnSuccessfullyCompiled extends WasSuccessfullyCompiled - case object WaitingForCompilationResult extends WasSuccessfullyCompiled - case object OutlinedByPC extends WasSuccessfullyCompiled - - def fromWasSuccessfullyCompiled( - wasSuccessfullyCompiled: Option[Boolean] - ): WasSuccessfullyCompiled = - wasSuccessfullyCompiled match { - case None => WaitingForCompilationResult - case Some(true) => SuccessfullyCompiled - case Some(false) => UnSuccessfullyCompiled - } } diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala index da2cadf11d5..fc2ecbfb3a4 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/Scala3CompilerWrapper.scala @@ -5,7 +5,7 @@ import dotty.tools.dotc.reporting.StoreReporter class Scala3CompilerWrapper(driver: MetalsDriver) extends CompilerWrapper[StoreReporter, MetalsDriver]: - override def compiler(paths: OutlineFiles): MetalsDriver = driver + override def compiler(): MetalsDriver = driver override def resetReporter(): Unit = val ctx = driver.currentCtx diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 936d2477314..e1fe6415fb4 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -182,7 +182,7 @@ case class ScalaPresentationCompiler( def shutdown(): Unit = compilerAccess.shutdown() - override def restart(): Unit = + def restart(): Unit = compilerAccess.shutdownCurrentCompiler() def diagnosticsForDebuggingPurposes(): ju.List[String] = diff --git a/mtags/src/main/scala/scala/meta/internal/metals/OffsetParamsUtils.scala b/mtags/src/main/scala/scala/meta/internal/metals/OffsetParamsUtils.scala index 8c449b181df..842e1e71a89 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/OffsetParamsUtils.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/OffsetParamsUtils.scala @@ -3,11 +3,13 @@ package scala.meta.internal.metals import java.net.URI import java.net.URISyntaxException import java.nio.file.Paths +import java.util.Optional import scala.meta.inputs.Position import scala.meta.internal.inputs.XtensionInputSyntaxStructure import scala.meta.pc.CancelToken import scala.meta.pc.OffsetParams +import scala.meta.pc.OutlineFiles trait OffsetParamsUtils { protected def syntaxURI(pos: Position): URI = { @@ -25,34 +27,48 @@ trait OffsetParamsUtils { object CompilerRangeParamsUtils extends OffsetParamsUtils { - def offsetOrRange(pos: Position, token: CancelToken): OffsetParams = { + def offsetOrRange( + pos: Position, + token: CancelToken, + outlineFiles: Optional[OutlineFiles] = Optional.empty() + ): OffsetParams = { if (pos.start == pos.end) - CompilerOffsetParamsUtils.fromPos(pos, token) + CompilerOffsetParamsUtils.fromPos(pos, token, outlineFiles) else - CompilerRangeParamsUtils.fromPos(pos, token) + CompilerRangeParamsUtils.fromPos(pos, token, outlineFiles) } - def fromPos(pos: Position, token: CancelToken): CompilerRangeParams = { + def fromPos( + pos: Position, + token: CancelToken, + outlineFiles: Optional[OutlineFiles] = Optional.empty() + ): CompilerRangeParams = { val uri = syntaxURI(pos) CompilerRangeParams( uri, pos.input.text, pos.start, pos.end, - token + token, + outlineFiles ) } } object CompilerOffsetParamsUtils extends OffsetParamsUtils { - def fromPos(pos: Position, token: CancelToken): CompilerOffsetParams = { + def fromPos( + pos: Position, + token: CancelToken, + outlineFiles: Optional[OutlineFiles] = Optional.empty() + ): CompilerOffsetParams = { val uri = syntaxURI(pos) CompilerOffsetParams( uri, pos.input.text, pos.start, - token + token, + outlineFiles ) } } diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index 35544e9c787..ef014537136 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -329,12 +329,12 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { | required: Int | val i: Int = "aaa" | ^^^^^ - |""".stripMargin + |""".stripMargin, ) _ <- assertCompletion( " def bar = foo.bar@@", "bar: String", - filename = Some("a/src/main/scala/c/C.scala") + filename = Some("a/src/main/scala/c/C.scala"), ) } yield () } diff --git a/tests/unit/src/test/scala/tests/codeactions/OrganizeImportsLspSuite.scala b/tests/unit/src/test/scala/tests/codeactions/OrganizeImportsLspSuite.scala index d778eed7b77..399671d1397 100644 --- a/tests/unit/src/test/scala/tests/codeactions/OrganizeImportsLspSuite.scala +++ b/tests/unit/src/test/scala/tests/codeactions/OrganizeImportsLspSuite.scala @@ -96,7 +96,7 @@ class OrganizeImportsLspSuite ) check( - "basic-unsaved-error", + "basic-unsaved-2", """ |package a |import scala.concurrent.duration._ @@ -110,8 +110,6 @@ class OrganizeImportsLspSuite |""".stripMargin, s"${SourceOrganizeImports.title}", """|package a - |import java.nio.file.ClassDoNotExist - |import scala.concurrent.ExecutionContext.global |import scala.concurrent.Future |import scala.concurrent.duration._ |// comment From ce2398758e0d1ffa0fcc3a1c92dbff2374ab3846 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 17 Apr 2024 14:22:06 +0200 Subject: [PATCH 11/20] move outline from `Compat` + move `semanticdbTextDocument` to `Compilers` --- .../meta/internal/metals/Compilers.scala | 101 +++++++++++++++++- .../metals/InteractiveSemanticdbs.scala | 3 +- .../metals/OutlineFilesProvider.scala | 14 +-- .../scala/meta/internal/pc/Compat.scala | 6 -- .../scala/meta/internal/pc/Outline.scala | 9 ++ .../scala/meta/internal/pc/Compat.scala | 37 +------ .../scala/meta/internal/pc/Outline.scala | 38 +++++++ .../meta/internal/pc/Compat.scala | 34 ------ .../meta/internal/pc/Compat.scala | 35 ------ .../meta/internal/pc/Compat.scala | 35 ------ .../scala/meta/internal/pc/Compat.scala | 35 ------ .../scala/meta/internal/pc/Outline.scala | 38 +++++++ .../scala/meta/internal/pc/MetalsGlobal.scala | 3 +- .../test/scala/tests/CompilersLspSuite.scala | 8 +- 14 files changed, 193 insertions(+), 203 deletions(-) create mode 100644 mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala create mode 100644 mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala create mode 100644 mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala 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 e62b6fc9465..b4a0acbc5d3 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -15,15 +15,18 @@ import scala.util.control.NonFatal import scala.meta.inputs.Input import scala.meta.inputs.Position +import scala.meta.internal.builds.SbtBuildTool import scala.meta.internal.metals.CompilerOffsetParamsUtils import scala.meta.internal.metals.CompilerRangeParamsUtils import scala.meta.internal.metals.Compilers.PresentationCompilerKey import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.internal.mtags.MD5 import scala.meta.internal.parsing.Trees import scala.meta.internal.pc.LogMessages import scala.meta.internal.pc.PcSymbolInformation import scala.meta.internal.worksheets.WorksheetPcData import scala.meta.internal.worksheets.WorksheetProvider +import scala.meta.internal.{semanticdb => s} import scala.meta.io.AbsolutePath import scala.meta.pc.AutoImportsResult import scala.meta.pc.CancelToken @@ -114,7 +117,8 @@ class Compilers( } val plugins = new CompilerPlugins() - val outlineFilesProvider = new OutlineFilesProvider(buildTargets, buffers) + private val outlineFilesProvider = + new OutlineFilesProvider(buildTargets, buffers) // Not a TrieMap because we want to avoid loading duplicate compilers for the same build target. // Not a `j.u.c.ConcurrentHashMap` because it can deadlock in `computeIfAbsent` when the absent @@ -1380,6 +1384,101 @@ class Compilers( debugItem } + def semanticdbTextDocument( + source: AbsolutePath, + text: String, + ): s.TextDocument = { + val (pc, optBuildTarget) = + loadCompiler(source).getOrElse((fallbackCompiler(source), None)) + + val (prependedLinesSize, modifiedText) = + Option + .when(source.isSbt)( + buildTargets + .sbtAutoImports(source) + ) + .flatten + .fold((0, text))(imports => + (imports.size, SbtBuildTool.prependAutoImports(text, imports)) + ) + + // NOTE(olafur): it's unfortunate that we block on `semanticdbTextDocument` + // here but to avoid it we would need to refactor the `Semanticdbs` trait, + // which requires more effort than it's worth. + val params = new CompilerVirtualFileParams( + source.toURI, + modifiedText, + token = EmptyCancelToken, + outlineFiles = outlineFilesProvider.getOutlineFiles(optBuildTarget), + ) + val bytes = pc + .semanticdbTextDocument(params) + .get( + config.initialConfig.compilers.timeoutDelay, + config.initialConfig.compilers.timeoutUnit, + ) + val textDocument = { + val doc = s.TextDocument.parseFrom(bytes) + if (doc.text.isEmpty()) doc.withText(text) + else doc + } + if (prependedLinesSize > 0) + cleanupAutoImports(textDocument, text, prependedLinesSize) + else textDocument + } + + private def cleanupAutoImports( + document: s.TextDocument, + originalText: String, + linesSize: Int, + ): s.TextDocument = { + + def adjustRange(range: s.Range): Option[s.Range] = { + val nextStartLine = range.startLine - linesSize + val nextEndLine = range.endLine - linesSize + if (nextEndLine >= 0) { + val nextRange = range.copy( + startLine = nextStartLine, + endLine = nextEndLine, + ) + Some(nextRange) + } else None + } + + val adjustedOccurences = + document.occurrences.flatMap { occurence => + occurence.range + .flatMap(adjustRange) + .map(r => occurence.copy(range = Some(r))) + } + + val adjustedDiagnostic = + document.diagnostics.flatMap { diagnostic => + diagnostic.range + .flatMap(adjustRange) + .map(r => diagnostic.copy(range = Some(r))) + } + + val adjustedSynthetic = + document.synthetics.flatMap { synthetic => + synthetic.range + .flatMap(adjustRange) + .map(r => synthetic.copy(range = Some(r))) + } + + s.TextDocument( + schema = document.schema, + uri = document.uri, + text = originalText, + md5 = MD5.compute(originalText), + language = document.language, + symbols = document.symbols, + occurrences = adjustedOccurences, + diagnostics = adjustedDiagnostic, + synthetics = adjustedSynthetic, + ) + } + } object Compilers { diff --git a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala index 619a0698701..c94d8906bdb 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala @@ -8,7 +8,6 @@ import scala.concurrent.duration.Duration import scala.util.Success import scala.util.Try -import scala.meta.internal.builds.SbtBuildTool import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals.scalacli.ScalaCliServers import scala.meta.internal.mtags.MD5 @@ -145,7 +144,7 @@ final class InteractiveSemanticdbs( javaInteractiveSemanticdb.fold(s.TextDocument())( _.textDocument(source, text) ) - else scalaCompile(source, text) + else compilers().semanticdbTextDocument(source, text) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala index e9ec7cdec5a..8a2c7b4ec0e 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala @@ -93,18 +93,8 @@ class OutlineFilesProvider( def enrichWithOutlineFiles( path: AbsolutePath - )(vFile: CompilerVirtualFileParams): CompilerVirtualFileParams = { - val optOutlineFiles = - for { - bt <- buildTargets.inferBuildTarget(path) - provider <- outlineFiles.get(bt) - outlineFiles <- provider.outlineFiles() - } yield outlineFiles - - optOutlineFiles - .map(outlineFiles => vFile.copy(outlineFiles = Optional.of(outlineFiles))) - .getOrElse(vFile) - } + )(vFile: CompilerVirtualFileParams): CompilerVirtualFileParams = + enrichWithOutlineFiles(buildTargets.inferBuildTarget(path))(vFile) def clear(): Unit = { outlineFiles.clear() 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 e4e177109fc..b3b68c9534e 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 @@ -3,8 +3,6 @@ package scala.meta.internal.pc import scala.tools.nsc.reporters.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.pc.OutlineFiles - trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = { val dealiased = tpe.dealiasWiden @@ -21,8 +19,4 @@ trait Compat { this: MetalsGlobal => def isAliasCompletion(m: Member): Boolean = false def constantType(c: ConstantType): ConstantType = c - - def runOutline(files: OutlineFiles): Unit = { - // no outline compilation for 2.11 - } } diff --git a/mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala b/mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala new file mode 100644 index 00000000000..67d16f13761 --- /dev/null +++ b/mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala @@ -0,0 +1,9 @@ +package scala.meta.internal.pc + +import scala.meta.pc.OutlineFiles + +trait Outline { this: MetalsGlobal => + 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 8532ab9e00d..da02d75b0f9 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 @@ -1,14 +1,8 @@ package scala.meta.internal.pc -import java.{util => ju} - -import scala.reflect.internal.Reporter +import scala.tools.nsc.reporters.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrSamArgTypes(tpe) @@ -22,33 +16,4 @@ trait Compat { this: MetalsGlobal => def isAliasCompletion(m: Member): Boolean = false def constantType(c: ConstantType): ConstantType = c - - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } } diff --git a/mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala b/mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala new file mode 100644 index 00000000000..a53f3fabe8f --- /dev/null +++ b/mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala @@ -0,0 +1,38 @@ +package scala.meta.internal.pc + +import java.{util => ju} + +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + +trait Outline { this: MetalsGlobal => + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } +} diff --git a/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala index 5f2527b504d..e492071a947 100644 --- a/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala @@ -1,14 +1,8 @@ package scala.meta.internal.pc -import java.{util => ju} - import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -26,32 +20,4 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } } diff --git a/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala index 5f2527b504d..988bd8f2db8 100644 --- a/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala @@ -1,14 +1,8 @@ package scala.meta.internal.pc -import java.{util => ju} - import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -25,33 +19,4 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c - - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } } diff --git a/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala index 1d941c7ebc2..e492071a947 100644 --- a/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala @@ -1,14 +1,8 @@ package scala.meta.internal.pc -import java.{util => ju} - import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -26,33 +20,4 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } - } 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 0b4fe757b10..a4eeed4b00c 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 @@ -1,14 +1,8 @@ package scala.meta.internal.pc -import java.{util => ju} - import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -29,33 +23,4 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c - - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } } diff --git a/mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala b/mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala new file mode 100644 index 00000000000..a53f3fabe8f --- /dev/null +++ b/mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala @@ -0,0 +1,38 @@ +package scala.meta.internal.pc + +import java.{util => ju} + +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + +trait Outline { this: MetalsGlobal => + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } +} 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 f8f598dbb82..f580398e421 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 @@ -62,7 +62,8 @@ class MetalsGlobal( with GlobalProxy with AutoImports with Keywords - with WorkspaceSymbolSearch { compiler => + with WorkspaceSymbolSearch + with Outline { compiler => hijackPresentationCompilerThread() val logger: Logger = Logger.getLogger(classOf[MetalsGlobal].getName) diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index ef014537136..eb2160e3951 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -230,7 +230,7 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { } yield () } - test("never-compiling-reverse-order") { + test("never-compiling2") { cleanWorkspace() for { _ <- initialize( @@ -261,8 +261,6 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { |} |""".stripMargin ) - _ <- server.didOpen("a/src/main/scala/a/A.scala") - _ <- server.didOpen("a/src/main/scala/b/B.scala") _ <- server.didOpen("a/src/main/scala/c/C.scala") _ = assertNoDiff( server.client.workspaceDiagnostics, @@ -287,7 +285,7 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { } yield () } - test("never-compiling2") { + test("never-compiling-reverse-order") { cleanWorkspace() for { _ <- initialize( @@ -319,8 +317,6 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { |} |""".stripMargin ) - _ <- server.didOpen("a/src/main/scala/a/A.scala") - _ <- server.didOpen("a/src/main/scala/b/B.scala") _ <- server.didOpen("a/src/main/scala/c/C.scala") _ = assertNoDiff( server.client.workspaceDiagnostics, From a4e914636815bf172b454cf1f43e907014537747 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 10:23:56 +0200 Subject: [PATCH 12/20] add `buildTargetId` to pc --- .../meta/internal/metals/Compilers.scala | 159 +++++++----------- .../metals/InteractiveSemanticdbs.scala | 2 - .../metals/OutlineFilesProvider.scala | 9 + .../scala/meta/pc/PresentationCompiler.java | 4 + .../metals/CompilerInlayHintsParams.scala | 2 +- .../pc/ScalaPresentationCompiler.scala | 2 + .../pc/ScalaPresentationCompiler.scala | 2 + 7 files changed, 80 insertions(+), 100 deletions(-) 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 b4a0acbc5d3..13d9d339992 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -104,18 +104,6 @@ class Compilers( import compilerConfiguration._ - class TargetCompilerFiles(targetId: BuildTargetIdentifier) - extends CompilerFiles { - override def allPaths(): ju.List[Path] = { - buildTargets - .buildTargetSources(targetId) - .flatMap(_.listRecursive.toList) - .map(_.toNIO) - .toList - .asJava - } - } - val plugins = new CompilerPlugins() private val outlineFilesProvider = new OutlineFilesProvider(buildTargets, buffers) @@ -216,9 +204,7 @@ class Compilers( } def didClose(path: AbsolutePath): Unit = { - loadCompiler(path).foreach { case (pc, _) => - pc.didClose(path.toNIO.toUri()) - } + loadCompiler(path).foreach(_.didClose(path.toNIO.toUri())) } def didChange(path: AbsolutePath): Future[List[Diagnostic]] = { @@ -227,7 +213,7 @@ class Compilers( .toInputFromBuffers(buffers) loadCompiler(path) - .map { case (pc, id) => + .map { pc => val inputAndAdjust = if ( path.isWorksheet && ScalaVersions.isScala3Version( @@ -244,7 +230,7 @@ class Compilers( AdjustedLspData.default, ) - id.foreach(outlineFilesProvider.didChange(_, path)) + outlineFilesProvider.didChange(pc.buildTargetId(), path) for { ds <- @@ -340,7 +326,7 @@ class Compilers( } } loadCompiler(path) - .map { case (compiler, buildTargetId) => + .map { compiler => val input = path.toInputFromBuffers(buffers) breakpointPosition.toMeta(input) match { case Some(metaPos) => @@ -381,7 +367,7 @@ class Compilers( modified, rangeEnd, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(compiler.buildTargetId()), ) val previousLines = expression @@ -436,7 +422,7 @@ class Compilers( } else { val path = params.getTextDocument.getUri.toAbsolutePath loadCompiler(path) - .map { case (compiler, buildTargetId) => + .map { compiler => val (input, _, adjust) = sourceAdjustments( params.getTextDocument().getUri(), @@ -541,7 +527,7 @@ class Compilers( path.toNIO.toUri(), input.text, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(compiler.buildTargetId()), ) val isScala3 = ScalaVersions.isScala3Version(compiler.scalaVersion()) @@ -583,7 +569,7 @@ class Compilers( params: InlayHintParams, token: CancelToken, ): Future[ju.List[InlayHint]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => def inlayHintsFallback( params: SyntheticDecorationsParams ): Future[ju.List[InlayHint]] = { @@ -624,7 +610,7 @@ class Compilers( CompilerRangeParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) val options = userConfig().inlayHintsOptions val pcParams = CompilerInlayHintsParams( @@ -655,8 +641,8 @@ class Compilers( params: CompletionParams, token: CancelToken, ): Future[CompletionList] = - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => - val outlineFiles = outlineFilesProvider.getOutlineFiles(buildTargetId) + withPCAndAdjustLsp(params) { (pc, pos, adjust) => + val outlineFiles = outlineFilesProvider.getOutlineFiles(pc.buildTargetId()) val offsetParams = CompilerOffsetParamsUtils.fromPos(pos, token, outlineFiles) pc.complete(offsetParams) @@ -673,13 +659,13 @@ class Compilers( findExtensionMethods: Boolean, token: CancelToken, ): Future[ju.List[AutoImportsResult]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.autoImports( name, CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ), findExtensionMethods, ).asScala @@ -694,12 +680,12 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.insertInferredType( CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala .map { edits => @@ -712,12 +698,12 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[TextEdit]] = - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.inlineValue( CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala .map(adjust.adjustTextEdits) @@ -727,12 +713,12 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[DocumentHighlight]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.documentHighlight( CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala .map { highlights => @@ -748,12 +734,12 @@ class Compilers( token: CancelToken, ): Future[ju.List[TextEdit]] = { withPCAndAdjustLsp(doc.getUri(), range, extractionPos) { - (pc, metaRange, metaExtractionPos, adjust, buildTargetId) => + (pc, metaRange, metaExtractionPos, adjust) => pc.extractMethod( CompilerRangeParamsUtils.fromPos( metaRange, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ), CompilerOffsetParamsUtils.fromPos(metaExtractionPos, token), ).asScala @@ -768,12 +754,12 @@ class Compilers( argIndices: ju.List[Integer], token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(position) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(position) { (pc, pos, adjust) => pc.convertToNamedArguments( CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ), argIndices, ).asScala @@ -787,12 +773,12 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.implementAbstractMembers( CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala .map { edits => @@ -805,12 +791,12 @@ class Compilers( params: HoverExtParams, token: CancelToken, ): Future[Option[HoverSignature]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.hover( CompilerRangeParamsUtils.offsetOrRange( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala .map(_.asScala.map { hover => adjust.adjustHoverResp(hover) }) @@ -821,12 +807,12 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[ju.Optional[LspRange]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.prepareRename( CompilerRangeParamsUtils.offsetOrRange( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala .map { range => @@ -839,12 +825,12 @@ class Compilers( params: RenameParams, token: CancelToken, ): Future[ju.List[TextEdit]] = { - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.rename( CompilerRangeParamsUtils.offsetOrRange( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ), params.getNewName(), ).asScala @@ -873,12 +859,11 @@ class Compilers( symbol: String, ): Future[Option[PcSymbolInformation]] = { loadCompiler(path, forceScala = true) - .map { case (pc, _) => - pc.info(symbol) + .map ( + _.info(symbol) .asScala .map(_.asScala.map(PcSymbolInformation.from)) - } - .getOrElse(Future(None)) + ).getOrElse(Future(None)) } private def definition( @@ -886,11 +871,11 @@ class Compilers( token: CancelToken, findTypeDef: Boolean, ): Future[DefinitionResult] = - withPCAndAdjustLsp(params) { (pc, pos, adjust, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, adjust) => val params = CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) val defResult = if (findTypeDef) pc.typeDefinition(params) @@ -926,12 +911,12 @@ class Compilers( params: TextDocumentPositionParams, token: CancelToken, ): Future[SignatureHelp] = - withPCAndAdjustLsp(params) { (pc, pos, _, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, pos, _) => pc.signatureHelp( CompilerOffsetParamsUtils.fromPos( pos, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ).asScala }.getOrElse(Future.successful(new SignatureHelp())) @@ -940,13 +925,13 @@ class Compilers( params: SelectionRangeParams, token: CancelToken, ): Future[ju.List[SelectionRange]] = { - withPCAndAdjustLsp(params) { (pc, positions, buildTargetId) => + withPCAndAdjustLsp(params) { (pc, positions) => val offsetPositions: ju.List[OffsetParams] = positions.map( CompilerOffsetParamsUtils.fromPos( _, token, - outlineFilesProvider.getOutlineFiles(buildTargetId), + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) ) pc.selectionRange(offsetPositions).asScala @@ -975,29 +960,27 @@ class Compilers( def loadCompiler( path: AbsolutePath, forceScala: Boolean = false, - ): Option[(PresentationCompiler, Option[BuildTargetIdentifier])] = { + ): Option[PresentationCompiler] = { - def fromBuildTarget: Option[(PresentationCompiler, Option[BuildTargetIdentifier])] = { + def fromBuildTarget: Option[PresentationCompiler] = { val target = buildTargets .inverseSources(path) target match { - case None => Some((fallbackCompiler(path), None)) + case None => Some(fallbackCompiler) case Some(value) => - if (path.isScalaFilename) loadCompiler(value).map((_, Some(value))) + if (path.isScalaFilename) loadCompiler(value) else if (path.isJavaFilename && forceScala) loadCompiler(value) - .orElse(loadJavaCompiler(value)) - .map((_, Some(value))) - else if (path.isJavaFilename) - loadJavaCompiler(value).map((_, Some(value))) + .orElse(Some(loadJavaCompiler(value))) + else if (path.isJavaFilename) Some(loadJavaCompiler(value)) else None } } if (!path.isScalaFilename && !path.isJavaFilename) None else if (path.isWorksheet) - loadWorksheetCompiler(path).map((_, None)).orElse(fromBuildTarget) + loadWorksheetCompiler(path).orElse(fromBuildTarget) else fromBuildTarget } @@ -1124,14 +1107,10 @@ class Compilers( private def withPCAndAdjustLsp[T]( params: SelectionRangeParams )( - fn: ( - PresentationCompiler, - ju.List[Position], - Option[BuildTargetIdentifier], - ) => T + fn: (PresentationCompiler, ju.List[Position]) => T ): Option[T] = { val path = params.getTextDocument.getUri.toAbsolutePath - loadCompiler(path).map { case (compiler, buildTargetId) => + loadCompiler(path).map { compiler => val input = path .toInputFromBuffers(buffers) .copy(path = params.getTextDocument.getUri) @@ -1139,22 +1118,17 @@ class Compilers( val positions = params.getPositions().asScala.flatMap(_.toMeta(input)).asJava - fn(compiler, positions, buildTargetId) + fn(compiler, positions) } } private def withPCAndAdjustLsp[T]( params: TextDocumentPositionParams )( - fn: ( - PresentationCompiler, - Position, - AdjustLspData, - Option[BuildTargetIdentifier], - ) => T + fn: (PresentationCompiler, Position, AdjustLspData) => T ): Option[T] = { val path = params.getTextDocument.getUri.toAbsolutePath - loadCompiler(path).flatMap { case (compiler, buildTargetId) => + loadCompiler(path).flatMap { compiler => val (input, pos, adjust) = sourceAdjustments( params, @@ -1162,22 +1136,17 @@ class Compilers( ) pos .toMeta(input) - .map(metaPos => fn(compiler, metaPos, adjust, buildTargetId)) + .map(metaPos => fn(compiler, metaPos, adjust)) } } private def withPCAndAdjustLsp[T]( params: InlayHintParams )( - fn: ( - PresentationCompiler, - Position, - AdjustLspData, - Option[BuildTargetIdentifier], - ) => T + fn: (PresentationCompiler, Position, AdjustLspData) => T ): Option[T] = { val path = params.getTextDocument.getUri.toAbsolutePath - loadCompiler(path).flatMap { case (compiler, buildTargetId) => + loadCompiler(path).flatMap { case compiler => val (input, pos, adjust) = sourceAdjustments( params, @@ -1185,7 +1154,7 @@ class Compilers( ) pos .toMeta(input) - .map(metaPos => fn(compiler, metaPos, adjust, buildTargetId)) + .map(metaPos => fn(compiler, metaPos, adjust)) } } @@ -1199,11 +1168,10 @@ class Compilers( Position, Position, AdjustLspData, - Option[BuildTargetIdentifier], ) => T ): Option[T] = { val path = uri.toAbsolutePath - loadCompiler(path).flatMap { case (compiler, buildTargetId) => + loadCompiler(path).flatMap { compiler => val (input, adjustRequest, adjustResponse) = sourceAdjustments( uri, @@ -1220,7 +1188,6 @@ class Compilers( metaRange, metaExtractionPos, adjustResponse, - buildTargetId, ) } } @@ -1232,18 +1199,17 @@ class Compilers( PresentationCompiler, Position, AdjustLspData, - Option[BuildTargetIdentifier], ) => T ): Option[T] = { val path = params.textDocument.getUri.toAbsolutePath - loadCompiler(path).flatMap { case (compiler, buildTargetId) => + loadCompiler(path).flatMap { compiler => if (params.range != null) { val (input, range, adjust) = sourceAdjustments( params, compiler.scalaVersion(), ) - range.toMeta(input).map(fn(compiler, _, adjust, buildTargetId)) + range.toMeta(input).map(fn(compiler, _, adjust)) } else { val positionParams = @@ -1255,7 +1221,7 @@ class Compilers( positionParams, compiler.scalaVersion(), ) - pos.toMeta(input).map(fn(compiler, _, adjust, buildTargetId)) + pos.toMeta(input).map(fn(compiler, _, adjust)) } } } @@ -1388,8 +1354,7 @@ class Compilers( source: AbsolutePath, text: String, ): s.TextDocument = { - val (pc, optBuildTarget) = - loadCompiler(source).getOrElse((fallbackCompiler(source), None)) + val pc = loadCompiler(source).getOrElse(fallbackCompiler) val (prependedLinesSize, modifiedText) = Option @@ -1409,7 +1374,7 @@ class Compilers( source.toURI, modifiedText, token = EmptyCancelToken, - outlineFiles = outlineFilesProvider.getOutlineFiles(optBuildTarget), + outlineFiles = outlineFilesProvider.getOutlineFiles(pc.buildTargetId()), ) val bytes = pc .semanticdbTextDocument(params) diff --git a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala index c94d8906bdb..2395178d7e9 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InteractiveSemanticdbs.scala @@ -3,8 +3,6 @@ package scala.meta.internal.metals import java.nio.charset.Charset import java.util.Collections -import scala.concurrent.Await -import scala.concurrent.duration.Duration import scala.util.Success import scala.util.Try diff --git a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala index 8a2c7b4ec0e..a495d7084ae 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala @@ -58,12 +58,18 @@ class OutlineFilesProvider( } } + def didChange(id: String, path: AbsolutePath): Unit = + buildTargetId(id).foreach(didChange(_, path)) + def didChange(id: BuildTargetIdentifier, path: AbsolutePath): Unit = { for { provider <- outlineFiles.get(id) } provider.didChange(path) } + def getOutlineFiles(id: String): Optional[JOutlineFiles] = + getOutlineFiles(buildTargetId(id)) + def getOutlineFiles( buildTargetId: Option[BuildTargetIdentifier] ): Optional[JOutlineFiles] = { @@ -99,6 +105,9 @@ class OutlineFilesProvider( def clear(): Unit = { outlineFiles.clear() } + + private def buildTargetId(id: String): Option[BuildTargetIdentifier] = + Option(id).filter(_.nonEmpty).map(new BuildTargetIdentifier(_)) } class BuildTargetOutlineFilesProvider( 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 5ffde6d79fd..4b7eebdbf03 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -332,4 +332,8 @@ public abstract PresentationCompiler newInstance(String buildTargetIdentifier, L */ public abstract String scalaVersion(); + public String buildTargetId() { + return ""; + } + } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala index 5d0e4246ca8..a6b29dde488 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/metals/CompilerInlayHintsParams.scala @@ -1,10 +1,10 @@ package scala.meta.internal.metals import java.net.URI +import java.util.Optional import scala.meta.pc.CancelToken import scala.meta.pc.InlayHintsParams -import java.util.Optional import scala.meta.pc.OutlineFiles case class CompilerInlayHintsParams( 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 3f00608c40e..f2e27467e88 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 @@ -459,6 +459,8 @@ case class ScalaPresentationCompiler( } } + override def buildTargetId(): String = buildTargetIdentifier + def newCompiler(): MetalsGlobal = { val classpath = this.classpath.mkString(File.pathSeparator) val vd = new VirtualDirectory("(memory)", None) diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index e1fe6415fb4..9119270d371 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -446,4 +446,6 @@ case class ScalaPresentationCompiler( override def isLoaded() = compilerAccess.isLoaded() + override def buildTargetId(): String = buildTargetIdentifier + end ScalaPresentationCompiler From 31366b7475c55b6d342347693c4daadad1833706 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 10:29:55 +0200 Subject: [PATCH 13/20] cap numer of files to outline --- .../metals/OutlineFilesProvider.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala index a495d7084ae..b6a5d9a8d4f 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala @@ -143,15 +143,19 @@ class BuildTargetOutlineFilesProvider( buildTargets .buildTargetSources(id) .flatMap(_.listRecursive.toList) - .flatMap(toVirtualFileParams(_)) - .toList - .asJava - Some( - OutlineFiles( - allFiles, - isFirstCompileSubstitute = true, + + if(allFiles.size > OutlineFilesProvider.maxOutlineFiles) { + // too many files to outline using pc + None + } else { + val inputs =allFiles.flatMap(toVirtualFileParams(_)).toList.asJava + Some( + OutlineFiles( + inputs, + isFirstCompileSubstitute = true, + ) ) - ) + } } else { changedDocuments.asScala.toList.flatMap( toVirtualFileParams @@ -188,3 +192,7 @@ case class OutlineFiles( files: ju.List[VirtualFileParams], isFirstCompileSubstitute: Boolean = false, ) extends JOutlineFiles + +object OutlineFilesProvider { + val maxOutlineFiles = 300 +} From aee71d84afd5f6cf3fb7672bec8a3fd827e732ee Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 11:32:56 +0200 Subject: [PATCH 14/20] add tests --- .../scala/meta/internal/pc/MetalsGlobal.scala | 1 - .../test/scala/tests/CompilersLspSuite.scala | 69 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) 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 f580398e421..f601c04dc4c 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 @@ -220,7 +220,6 @@ class MetalsGlobal( else false } - // TODO the same in autoimports TEST if (query.isEmpty) SymbolSearch.Result.INCOMPLETE else { val context = doLocateContext(pos) diff --git a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala index eb2160e3951..416781d961e 100644 --- a/tests/unit/src/test/scala/tests/CompilersLspSuite.scala +++ b/tests/unit/src/test/scala/tests/CompilersLspSuite.scala @@ -135,7 +135,7 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { |} |""".stripMargin, ) - // check if the change name is piecked up despite the file not compiling + // check if the change name is picked up despite the file not compiling newText = """|package a | |class A { @@ -334,4 +334,71 @@ class CompilersLspSuite extends BaseCompletionLspSuite("compilers") { ) } yield () } + + test("imports-for-non-compiling") { + cleanWorkspace() + for { + _ <- initialize( + """/metals.json + |{ + | "a": {} + |} + |/a/src/main/scala/a/A.scala + |package a + | + |/a/src/main/scala/b/B.scala + |package b + | + |object O { + | class UniqueObject { + | } + |} + |""".stripMargin + ) + _ <- server.didOpen("a/src/main/scala/b/B.scala") + _ <- server.didChange("a/src/main/scala/b/B.scala") { _ => + """|package b + |object W { + | class UniqueObject { + | val i: Int = "aaa" + | } + |} + |""".stripMargin + } + _ <- server.didSave("a/src/main/scala/b/B.scala")(identity) + // check if the change name is picked up despite the file not compiling + newText = """|package a + | + |object A { + | // @@ + | val k: <> = ??? + |} + |""".stripMargin + input = newText.replace("<<", "").replace(">>", "") + _ <- server.didChange("a/src/main/scala/a/A.scala")(_ => input) + _ <- server.didSave("a/src/main/scala/a/A.scala")(identity) + codeActions <- + server + .assertCodeAction( + "a/src/main/scala/a/A.scala", + newText, + s"""|${ImportMissingSymbol.title("UniqueObject", "b.W")} + |${CreateNewSymbol.title("UniqueObject")} + |""".stripMargin, + kind = Nil, + ) + _ <- assertCompletionEdit( + "UniqueObject@@", + """|package a + | + |import b.W.UniqueObject + | + |object A { + | UniqueObject + | val k: UniqueObject = ??? + |} + |""".stripMargin, + ) + } yield () + } } From 94e70083a8ca7653d8dc15796a2bd84c3cbe9ef0 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 11:33:22 +0200 Subject: [PATCH 15/20] format --- .../scala/scala/meta/internal/metals/Compilers.scala | 11 ++++++----- .../meta/internal/metals/OutlineFilesProvider.scala | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) 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 13d9d339992..328fd656b41 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -642,7 +642,8 @@ class Compilers( token: CancelToken, ): Future[CompletionList] = withPCAndAdjustLsp(params) { (pc, pos, adjust) => - val outlineFiles = outlineFilesProvider.getOutlineFiles(pc.buildTargetId()) + val outlineFiles = + outlineFilesProvider.getOutlineFiles(pc.buildTargetId()) val offsetParams = CompilerOffsetParamsUtils.fromPos(pos, token, outlineFiles) pc.complete(offsetParams) @@ -859,11 +860,11 @@ class Compilers( symbol: String, ): Future[Option[PcSymbolInformation]] = { loadCompiler(path, forceScala = true) - .map ( - _.info(symbol) - .asScala + .map( + _.info(symbol).asScala .map(_.asScala.map(PcSymbolInformation.from)) - ).getOrElse(Future(None)) + ) + .getOrElse(Future(None)) } private def definition( diff --git a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala index b6a5d9a8d4f..af7876d197e 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/OutlineFilesProvider.scala @@ -144,11 +144,11 @@ class BuildTargetOutlineFilesProvider( .buildTargetSources(id) .flatMap(_.listRecursive.toList) - if(allFiles.size > OutlineFilesProvider.maxOutlineFiles) { + if (allFiles.size > OutlineFilesProvider.maxOutlineFiles) { // too many files to outline using pc None } else { - val inputs =allFiles.flatMap(toVirtualFileParams(_)).toList.asJava + val inputs = allFiles.flatMap(toVirtualFileParams(_)).toList.asJava Some( OutlineFiles( inputs, From 31b3282171c22f8652aea05cbe4086dfd07ca424 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 12:49:45 +0200 Subject: [PATCH 16/20] reverse moving outline from `Compat` --- .../scala/meta/internal/pc/Compat.scala | 6 +++ .../scala/meta/internal/pc/Outline.scala | 9 ----- .../scala/meta/internal/pc/Compat.scala | 37 +++++++++++++++++- .../scala/meta/internal/pc/Outline.scala | 38 ------------------- .../meta/internal/pc/Compat.scala | 34 +++++++++++++++++ .../meta/internal/pc/Compat.scala | 35 +++++++++++++++++ .../meta/internal/pc/Compat.scala | 35 +++++++++++++++++ .../scala/meta/internal/pc/Compat.scala | 35 +++++++++++++++++ .../scala/meta/internal/pc/Outline.scala | 38 ------------------- .../scala/meta/internal/pc/MetalsGlobal.scala | 3 +- 10 files changed, 182 insertions(+), 88 deletions(-) delete mode 100644 mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala delete mode 100644 mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala delete mode 100644 mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala 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 b3b68c9534e..e4e177109fc 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 @@ -3,6 +3,8 @@ package scala.meta.internal.pc import scala.tools.nsc.reporters.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.pc.OutlineFiles + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = { val dealiased = tpe.dealiasWiden @@ -19,4 +21,8 @@ trait Compat { this: MetalsGlobal => def isAliasCompletion(m: Member): Boolean = false def constantType(c: ConstantType): ConstantType = c + + def runOutline(files: OutlineFiles): Unit = { + // no outline compilation for 2.11 + } } diff --git a/mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala b/mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala deleted file mode 100644 index 67d16f13761..00000000000 --- a/mtags/src/main/scala-2.11/scala/meta/internal/pc/Outline.scala +++ /dev/null @@ -1,9 +0,0 @@ -package scala.meta.internal.pc - -import scala.meta.pc.OutlineFiles - -trait Outline { this: MetalsGlobal => - 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 da02d75b0f9..8532ab9e00d 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 @@ -1,8 +1,14 @@ package scala.meta.internal.pc -import scala.tools.nsc.reporters.Reporter +import java.{util => ju} + +import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrSamArgTypes(tpe) @@ -16,4 +22,33 @@ trait Compat { this: MetalsGlobal => def isAliasCompletion(m: Member): Boolean = false def constantType(c: ConstantType): ConstantType = c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } diff --git a/mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala b/mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala deleted file mode 100644 index a53f3fabe8f..00000000000 --- a/mtags/src/main/scala-2.12/scala/meta/internal/pc/Outline.scala +++ /dev/null @@ -1,38 +0,0 @@ -package scala.meta.internal.pc - -import java.{util => ju} - -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - -trait Outline { this: MetalsGlobal => - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } -} diff --git a/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala index e492071a947..5f2527b504d 100644 --- a/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.5/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -20,4 +26,32 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } diff --git a/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala index 988bd8f2db8..5f2527b504d 100644 --- a/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.6/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -19,4 +25,33 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } diff --git a/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala b/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala index e492071a947..1d941c7ebc2 100644 --- a/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala +++ b/mtags/src/main/scala-2.13.7/meta/internal/pc/Compat.scala @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -20,4 +26,33 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } + } 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 a4eeed4b00c..0b4fe757b10 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 @@ -1,8 +1,14 @@ package scala.meta.internal.pc +import java.{util => ju} + import scala.reflect.internal.Reporter import scala.tools.nsc.reporters.StoreReporter +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.OutlineFiles +import scala.meta.pc.VirtualFileParams + trait Compat { this: MetalsGlobal => def metalsFunctionArgTypes(tpe: Type): List[Type] = definitions.functionOrPfOrSamArgTypes(tpe) @@ -23,4 +29,33 @@ trait Compat { this: MetalsGlobal => def constantType(c: ConstantType): ConstantType = if (c.value.isSuitableLiteralType) LiteralType(c.value) else c + + def runOutline(files: OutlineFiles): Unit = { + this.settings.Youtline.value = true + runOutline(files.files) + if (files.isFirstCompileSubstitute()) { + // 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: ju.List[VirtualFileParams], + forceNewUnit: Boolean = false + ): Unit = { + files.asScala.foreach { params => + val unit = this.addCompilationUnit( + params.text(), + params.uri.toString(), + cursor = None, + isOutline = true, + forceNew = forceNewUnit + ) + this.typeCheck(unit) + this.richCompilationCache.put(params.uri().toString(), unit) + } + } } diff --git a/mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala b/mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala deleted file mode 100644 index a53f3fabe8f..00000000000 --- a/mtags/src/main/scala-2.13/scala/meta/internal/pc/Outline.scala +++ /dev/null @@ -1,38 +0,0 @@ -package scala.meta.internal.pc - -import java.{util => ju} - -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.pc.OutlineFiles -import scala.meta.pc.VirtualFileParams - -trait Outline { this: MetalsGlobal => - def runOutline(files: OutlineFiles): Unit = { - this.settings.Youtline.value = true - runOutline(files.files) - if (files.isFirstCompileSubstitute()) { - // 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: ju.List[VirtualFileParams], - forceNewUnit: Boolean = false - ): Unit = { - files.asScala.foreach { params => - val unit = this.addCompilationUnit( - params.text(), - params.uri.toString(), - cursor = None, - isOutline = true, - forceNew = forceNewUnit - ) - this.typeCheck(unit) - this.richCompilationCache.put(params.uri().toString(), unit) - } - } -} 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 f601c04dc4c..2f9a043f83e 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 @@ -62,8 +62,7 @@ class MetalsGlobal( with GlobalProxy with AutoImports with Keywords - with WorkspaceSymbolSearch - with Outline { compiler => + with WorkspaceSymbolSearch { compiler => hijackPresentationCompilerThread() val logger: Logger = Logger.getLogger(classOf[MetalsGlobal].getName) From ff0420b4d080d9d8c5e2ef55fa21a44f7b64abab Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 23 Apr 2024 11:27:37 +0200 Subject: [PATCH 17/20] formatting fix --- .../scala/meta/internal/pc/ScalaPresentationCompiler.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 f2e27467e88..8659ca01df8 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 @@ -365,7 +365,11 @@ case class ScalaPresentationCompiler( params.token ) { pc => Optional.ofNullable( - new HoverProvider(pc.compiler(params), params, config.hoverContentType()) + new HoverProvider( + pc.compiler(params), + params, + config.hoverContentType() + ) .hover() .orNull ) From 3dfc44a887cf23dcf38a3dc5400d0dfc8b70d052 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 26 Apr 2024 09:56:07 +0200 Subject: [PATCH 18/20] review fixes --- .../scala/scala/meta/internal/metals/Compilers.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 328fd656b41..4018e32c346 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -104,7 +104,6 @@ class Compilers( import compilerConfiguration._ - val plugins = new CompilerPlugins() private val outlineFilesProvider = new OutlineFilesProvider(buildTargets, buffers) @@ -968,7 +967,15 @@ class Compilers( .inverseSources(path) target match { - case None => Some(fallbackCompiler) + case None => + val tmpDirectory = workspace.resolve(Directories.tmp) + val scalaVersion = + scalaVersionSelector.fallbackScalaVersion(isAmmonite = false) + if (!path.toNIO.startsWith(tmpDirectory.toNIO)) + scribe.info( + s"no build target found for $path. Using presentation compiler with project's scala-library version: ${scalaVersion}" + ) + Some(fallbackCompiler) case Some(value) => if (path.isScalaFilename) loadCompiler(value) else if (path.isJavaFilename && forceScala) From 1267738da046c0ef47673699b33f7ce7ed705131 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 30 Apr 2024 12:20:19 +0200 Subject: [PATCH 19/20] show only syms from defs --- .../scala/meta/internal/pc/AutoImportsProvider.scala | 6 +++--- .../scala/meta/internal/pc/WorkspaceSymbolSearch.scala | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala index 0dcb161221b..4ef9095584c 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/AutoImportsProvider.scala @@ -30,7 +30,7 @@ final class AutoImportsProvider( val isSeen = mutable.Set.empty[String] val symbols = List.newBuilder[Symbol] - def visit(fromWorkspace: Boolean)(sym: Symbol): Boolean = { + def visit(sym: Symbol): Boolean = { val id = sym.fullName if (!isSeen(id)) { isSeen += id @@ -40,10 +40,10 @@ final class AutoImportsProvider( false } - compiler.searchOutline(visit(fromWorkspace = false), name) + compiler.searchOutline(visit, name) val visitor = - new CompilerSearchVisitor(context, visit(fromWorkspace = true)) + new CompilerSearchVisitor(context, visit) search.search(name, buildTargetIdentifier, visitor) def isInImportTree: Boolean = lastVisitedParentTrees match { 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 3344b54bd14..682a6098b3f 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 @@ -23,7 +23,7 @@ trait WorkspaceSymbolSearch { compiler: MetalsGlobal => def loop(trees: List[Tree]): Unit = { trees match { case Nil => - case tree :: tail => + case (tree: MemberDef) :: tail => val sym = tree.symbol def matches = if (sym.isType) CompletionFuzzy.matchesSubCharacters(query, sym.name.toString()) @@ -37,6 +37,8 @@ trait WorkspaceSymbolSearch { compiler: MetalsGlobal => } } loop(tree.children ++ tail) + case tree :: tail => + loop(tree.children ++ tail) } } loop(List(unit.body)) From 5a327c48e3ab9d3faae7e836ee7bd825f1db7de1 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 20 May 2024 11:41:47 +0200 Subject: [PATCH 20/20] post rebase fixes --- .../meta/internal/metals/MetalsLspService.scala | 12 ++++++------ .../internal/metals/ProjectMetalsLspService.scala | 3 +-- .../meta/internal/pc/WorkspaceSymbolSearch.scala | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 532b5e6fc4f..9e13818e2cc 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -408,6 +408,11 @@ abstract class MetalsLspService( }, ) + protected def onCreate(path: AbsolutePath): Unit = { + buildTargets.onCreate(path) + compilers.didChange(path) + } + protected val interactiveSemanticdbs: InteractiveSemanticdbs = { val javaInteractiveSemanticdb = maybeJdkVersion.map(jdkVersion => JavaInteractiveSemanticdb.create(folder, buildTargets, jdkVersion) @@ -867,12 +872,7 @@ abstract class MetalsLspService( buffers.put(path, change.getText) diagnostics.didChange(path) compilers.didChange(path) - parseTrees(path) - .map { _ => - treeView.onWorkspaceFileDidChange(path) - } - .ignoreValue - .asJava + parseTrees(path).asJava } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala index 72e3f72f665..4f82ee779b8 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala @@ -574,8 +574,7 @@ class ProjectMetalsLspService( !buffers.contains(path) ) { event.eventType match { - case EventType.CreateOrModify => - buildTargets.onCreate(path) + case EventType.CreateOrModify => onCreate(path) case _ => } onChange(List(path)).asJava 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 682a6098b3f..8e42984fe5b 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 @@ -16,7 +16,7 @@ trait WorkspaceSymbolSearch { compiler: MetalsGlobal => def searchOutline( visitMember: Symbol => Boolean, query: String - ) { + ): Unit = { def traverseUnit(unit: RichCompilationUnit) = { @tailrec