diff --git a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala index f83f12e1c027..e77642a8e2b9 100644 --- a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala +++ b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -3,6 +3,7 @@ package ast import core.Contexts.* import core.Decorators.* +import core.StdNames import util.Spans.* import Trees.{Closure, MemberDef, DefTree, WithLazyFields} import dotty.tools.dotc.core.Types.AnnotatedType @@ -74,21 +75,50 @@ object NavigateAST { def pathTo(span: Span, from: List[Positioned], skipZeroExtent: Boolean = false)(using Context): List[Positioned] = { def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = { var bestFit: List[Positioned] = path - while (it.hasNext) { - val path1 = it.next() match { + while (it.hasNext) do + val path1 = it.next() match + case sel: untpd.Select if isRecoveryTree(sel) => path + case sel: untpd.Ident if isPatternRecoveryTree(sel) => path case p: Positioned if !p.isInstanceOf[Closure[?]] => singlePath(p, path) case m: untpd.Modifiers => childPath(m.productIterator, path) case xs: List[?] => childPath(xs.iterator, path) case _ => path - } - if ((path1 ne path) && - ((bestFit eq path) || - bestFit.head.span != path1.head.span && - bestFit.head.span.contains(path1.head.span))) + + if (path1 ne path) && ((bestFit eq path) || isBetterFit(bestFit, path1)) then bestFit = path1 - } + bestFit } + + /** + * When choosing better fit we compare spans. If candidate span has starting or ending point inside (exclusive) + * current best fit it is selected as new best fit. This means that same spans are failing the first predicate. + * + * In case when spans start and end at same offsets we prefer non synthethic one. + */ + def isBetterFit(currentBest: List[Positioned], candidate: List[Positioned]): Boolean = + if currentBest.isEmpty && candidate.nonEmpty then true + else if currentBest.nonEmpty && candidate.nonEmpty then + val bestSpan = currentBest.head.span + val candidateSpan = candidate.head.span + + bestSpan != candidateSpan && + envelops(bestSpan, candidateSpan) || + bestSpan.contains(candidateSpan) && bestSpan.isSynthetic && !candidateSpan.isSynthetic + else false + + def isRecoveryTree(sel: untpd.Select): Boolean = + sel.span.isSynthetic + && (sel.name == StdNames.nme.??? && sel.qualifier.symbol.name == StdNames.nme.Predef) + + def isPatternRecoveryTree(ident: untpd.Ident): Boolean = + ident.span.isSynthetic && StdNames.nme.WILDCARD == ident.name + + def envelops(a: Span, b: Span): Boolean = + !b.exists || a.exists && ( + (a.start < b.start && a.end >= b.end ) || (a.start <= b.start && a.end > b.end) + ) + /* * Annotations trees are located in the Type */ diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 1395d9b80b53..7112caf1cfad 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -121,16 +121,17 @@ object Completion: case _ => "" + def naiveCompletionPrefix(text: String, offset: Int): String = + var i = offset - 1 + while i >= 0 && text(i).isUnicodeIdentifierPart do i -= 1 + i += 1 // move to first character + text.slice(i, offset) + /** * Inspect `path` to determine the completion prefix. Only symbols whose name start with the * returned prefix should be considered. */ def completionPrefix(path: List[untpd.Tree], pos: SourcePosition)(using Context): String = - def fallback: Int = - var i = pos.point - 1 - while i >= 0 && Character.isUnicodeIdentifierPart(pos.source.content()(i)) do i -= 1 - i + 1 - path match case GenericImportSelector(sel) => if sel.isGiven then completionPrefix(sel.bound :: Nil, pos) @@ -148,7 +149,7 @@ object Completion: case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR => tree.name.toString.take(pos.span.point - tree.span.point) - case _ => pos.source.content.slice(fallback, pos.point).mkString + case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point) end completionPrefix diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 39bdc69111d3..8a173faa3cec 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -406,7 +406,7 @@ object Parsers { false } - def errorTermTree(start: Offset): Tree = atSpan(start, in.offset, in.offset) { unimplementedExpr } + def errorTermTree(start: Offset): Tree = atSpan(Span(start, in.offset)) { unimplementedExpr } private var inFunReturnType = false private def fromWithinReturnType[T](body: => T): T = { diff --git a/language-server/test/dotty/tools/languageserver/HoverTest.scala b/language-server/test/dotty/tools/languageserver/HoverTest.scala index a2196f4a71f3..91f72e222432 100644 --- a/language-server/test/dotty/tools/languageserver/HoverTest.scala +++ b/language-server/test/dotty/tools/languageserver/HoverTest.scala @@ -227,7 +227,7 @@ class HoverTest { @Test def enums: Unit = { code"""|package example |enum TestEnum3: - | case ${m1}A${m2} // no tooltip + | case ${m1}A${m2} // no tooltip | |""" .hover(m1 to m2, hoverContent("example.TestEnum3")) diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala index ded7845ffa4e..e35556ad11c9 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala @@ -13,7 +13,6 @@ import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourceFile -import dotty.tools.pc.AutoImports.* import dotty.tools.pc.completions.CompletionPos import dotty.tools.pc.utils.InteractiveEnrichments.* @@ -67,7 +66,8 @@ final class AutoImportsProvider( val results = symbols.result.filter(isExactMatch(_, name)) if results.nonEmpty then - val correctedPos = CompletionPos.infer(pos, params, path).toSourcePosition + val correctedPos = + CompletionPos.infer(pos, params, path, wasCursorApplied = false).toSourcePosition val mkEdit = path match // if we are in import section just specify full name diff --git a/presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala b/presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala similarity index 88% rename from presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala rename to presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala index 819c3f2fc9c9..f5715c2780a9 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala @@ -10,8 +10,8 @@ import dotty.tools.dotc.util.SourceFile import scala.compiletime.uninitialized /** - * MetalsDriver is a wrapper class that provides a compilation cache for InteractiveDriver. - * MetalsDriver skips running compilation if + * CachingDriver is a wrapper class that provides a compilation cache for InteractiveDriver. + * CachingDriver skips running compilation if * - the target URI of `run` is the same as the previous target URI * - the content didn't change since the last compilation. * @@ -27,9 +27,7 @@ import scala.compiletime.uninitialized * To avoid the complexity related to currentCtx, * we decided to cache only when the target URI only if the same as the previous run. */ -class MetalsDriver( - override val settings: List[String] -) extends InteractiveDriver(settings): +class CachingDriver(override val settings: List[String]) extends InteractiveDriver(settings): @volatile private var lastCompiledURI: URI = uninitialized @@ -55,4 +53,4 @@ class MetalsDriver( lastCompiledURI = uri diags -end MetalsDriver +end CachingDriver diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index b3f836801460..9c0e6bcfa9d8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -14,7 +14,6 @@ import scala.meta.internal.pc.LabelPart.* import scala.meta.pc.InlayHintsParams import scala.meta.pc.SymbolSearch -import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags diff --git a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala index ef5aaf4e5ed0..1443fbcf37cc 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala @@ -8,13 +8,14 @@ import scala.meta.internal.pc.CompilerAccess import scala.meta.pc.PresentationCompilerConfig import dotty.tools.dotc.reporting.StoreReporter +import dotty.tools.dotc.interactive.InteractiveDriver class Scala3CompilerAccess( config: PresentationCompilerConfig, sh: Option[ScheduledExecutorService], newCompiler: () => Scala3CompilerWrapper )(using ec: ExecutionContextExecutor, rc: ReportContext) - extends CompilerAccess[StoreReporter, MetalsDriver]( + extends CompilerAccess[StoreReporter, InteractiveDriver]( config, sh, newCompiler, diff --git a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala index de4fb282edc9..968c144625a3 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala @@ -4,11 +4,12 @@ import scala.meta.internal.pc.CompilerWrapper import scala.meta.internal.pc.ReporterAccess import dotty.tools.dotc.reporting.StoreReporter +import dotty.tools.dotc.interactive.InteractiveDriver -class Scala3CompilerWrapper(driver: MetalsDriver) - extends CompilerWrapper[StoreReporter, MetalsDriver]: +class Scala3CompilerWrapper(driver: InteractiveDriver) + extends CompilerWrapper[StoreReporter, InteractiveDriver]: - override def compiler(): MetalsDriver = driver + override def compiler(): InteractiveDriver = driver override def resetReporter(): Unit = val ctx = driver.currentCtx diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index 85de8e7d8439..e6da8b79164f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -33,11 +33,13 @@ import dotty.tools.pc.completions.CompletionProvider import dotty.tools.pc.InferExpectedType import dotty.tools.pc.completions.OverrideCompletions import dotty.tools.pc.buildinfo.BuildInfo +import dotty.tools.pc.SymbolInformationProvider +import dotty.tools.dotc.interactive.InteractiveDriver import org.eclipse.lsp4j.DocumentHighlight import org.eclipse.lsp4j.TextEdit import org.eclipse.lsp4j as l -import dotty.tools.pc.SymbolInformationProvider + case class ScalaPresentationCompiler( buildTargetIdentifier: String = "", @@ -76,14 +78,20 @@ case class ScalaPresentationCompiler( override def withReportsLoggerLevel(level: String): PresentationCompiler = copy(reportsLevel = ReportLevel.fromString(level)) - val compilerAccess: CompilerAccess[StoreReporter, MetalsDriver] = + val compilerAccess: CompilerAccess[StoreReporter, InteractiveDriver] = Scala3CompilerAccess( config, sh, - () => new Scala3CompilerWrapper(newDriver) - )(using - ec - ) + () => new Scala3CompilerWrapper(CachingDriver(driverSettings)) + )(using ec) + + val driverSettings = + val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0") + val defaultFlags = List("-color:never") + val filteredOptions = removeDoubleOptions(options.filterNot(forbiddenOptions)) + + filteredOptions ::: defaultFlags ::: implicitSuggestionTimeout ::: "-classpath" :: classpath + .mkString(File.pathSeparator) :: Nil private def removeDoubleOptions(options: List[String]): List[String] = options match @@ -92,19 +100,6 @@ case class ScalaPresentationCompiler( case head :: tail => head :: removeDoubleOptions(tail) case Nil => options - def newDriver: MetalsDriver = - val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0") - val defaultFlags = List("-color:never") - val filteredOptions = removeDoubleOptions( - options.filterNot(forbiddenOptions) - ) - val settings = - filteredOptions ::: defaultFlags ::: implicitSuggestionTimeout ::: "-classpath" :: classpath - .mkString( - File.pathSeparator - ) :: Nil - new MetalsDriver(settings) - override def semanticTokens( params: VirtualFileParams ): CompletableFuture[ju.List[Node]] = @@ -146,6 +141,7 @@ case class ScalaPresentationCompiler( new CompletionProvider( search, driver, + () => InteractiveDriver(driverSettings), params, config, buildTargetIdentifier, diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala b/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala index 2bb8023cee08..5a4c135fdc4c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala @@ -1,6 +1,5 @@ package dotty.tools.pc -import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Comments.Comment object ScriptFirstImportPosition: diff --git a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala index edfd9c95fa84..bd16d2ce2aa9 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala @@ -1,6 +1,5 @@ package dotty.tools.pc -import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Symbols.* diff --git a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala index da075e21f486..ccda618078b8 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala @@ -7,8 +7,6 @@ import scala.meta.pc.PcSymbolKind import scala.meta.pc.PcSymbolProperty import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Denotations.Denotation -import dotty.tools.dotc.core.Denotations.MultiDenotation import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.core.StdNames.nme @@ -19,6 +17,7 @@ import dotty.tools.pc.utils.InteractiveEnrichments.allSymbols import dotty.tools.pc.utils.InteractiveEnrichments.stripBackticks import scala.meta.internal.pc.PcSymbolInformation import scala.meta.internal.pc.SymbolInfo +import dotty.tools.dotc.core.Denotations.{Denotation, MultiDenotation} class SymbolInformationProvider(using Context): diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala index ad571ff843c3..6d89cb663b9c 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala @@ -22,7 +22,8 @@ case class CompletionPos( identEnd: Int, query: String, originalCursorPosition: SourcePosition, - sourceUri: URI + sourceUri: URI, + withCURSOR: Boolean ): def queryEnd: Int = originalCursorPosition.point def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd)) @@ -34,17 +35,19 @@ object CompletionPos: def infer( sourcePos: SourcePosition, offsetParams: OffsetParams, - adjustedPath: List[Tree] + adjustedPath: List[Tree], + wasCursorApplied: Boolean )(using Context): CompletionPos = val identEnd = adjustedPath match case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) => refTree.span.end - Cursor.value.length + case (refTree: RefTree) :: _ => refTree.span.end case _ => sourcePos.end val query = Completion.completionPrefix(adjustedPath, sourcePos) val start = sourcePos.end - query.length() - CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn) + CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied) /** * Infer the indentation by counting the number of spaces in the given line. diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala index 4d45595dac8d..5578fab412d1 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala @@ -14,9 +14,13 @@ import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Phases -import dotty.tools.dotc.core.StdNames +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Flags +import dotty.tools.dotc.core.Names.DerivedName import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.Completion import dotty.tools.dotc.interactive.InteractiveDriver +import dotty.tools.dotc.parsing.Tokens import dotty.tools.dotc.util.SourceFile import dotty.tools.pc.AutoImports.AutoImportEdits import dotty.tools.pc.AutoImports.AutoImportsGenerator @@ -34,9 +38,15 @@ import org.eclipse.lsp4j.Range as LspRange import org.eclipse.lsp4j.TextEdit import scala.meta.pc.CompletionItemPriority +object CompletionProvider: + val allKeywords = + val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using + Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString) + class CompletionProvider( search: SymbolSearch, - driver: InteractiveDriver, + cachingDriver: InteractiveDriver, + freshDriver: () => InteractiveDriver, params: OffsetParams, config: PresentationCompilerConfig, buildTargetIdentifier: String, @@ -47,23 +57,55 @@ class CompletionProvider( val uri = params.uri().nn val text = params.text().nn - val code = applyCompletionCursor(params) + val (wasCursorApplied, code) = applyCompletionCursor(params) val sourceFile = SourceFile.virtual(uri, code) + + /** Creating a new fresh driver is way slower than reusing existing one, + * but runnig a compilation has side effects that modifies the state of the driver. + * We don't want to affect cachingDriver state with compilation including "CURSOR" suffix. + * + * We could in theory save this fresh driver for reuse, but it is a choice between extra memory usage and speed. + * The scenario in which "CURSOR" is applied (empty query or query equal to any keyword) has a slim chance of happening. + */ + + val driver = if wasCursorApplied then freshDriver() else cachingDriver driver.run(uri, sourceFile) - val ctx = driver.currentCtx + given ctx: Context = driver.currentCtx val pos = driver.sourcePosition(params) val (items, isIncomplete) = driver.compilationUnits.get(uri) match case Some(unit) => - val newctx = ctx.fresh.setCompilationUnit(unit).withPhase(Phases.typerPhase(using ctx)) - val tpdPath = Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx) - val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)(using newctx) + val tpdPath0 = Interactive.pathTo(unit.tpdTree, pos.span)(using newctx) + val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath0, pos)(using newctx) + + val tpdPath = tpdPath0 match + case Select(qual, name) :: tail + /** If for any reason we end up in param after lifting, we want to inline the synthetic val: + * List(1).iterator.sliding@@ will be transformed into: + * + * 1| val $1$: Iterator[Int] = List.apply[Int]([1 : Int]*).iterator + * 2| { + * 3| def $anonfun(size: Int, step: Int): $1$.GroupedIterator[Int] = + * 4| $1$.sliding[Int](size, step) + * 5| closure($anonfun) + * 6| }:((Int, Int) => Iterator[Int]#GroupedIterator[Int]) + * + * With completion being run at line 4 at @@: + * 4| $1$.sliding@@[Int](size, step) + * + */ + if qual.symbol.is(Flags.Synthetic) && qual.symbol.name.isInstanceOf[DerivedName] => + qual.symbol.defTree match + case valdef: ValDef => Select(valdef.rhs, name) :: tail + case _ => tpdPath0 + case _ => tpdPath0 + val locatedCtx = Interactive.contextOfPath(tpdPath)(using newctx) val indexedCtx = IndexedContext(locatedCtx) - val completionPos = CompletionPos.infer(pos, params, adjustedPath)(using locatedCtx) + val completionPos = CompletionPos.infer(pos, params, adjustedPath, wasCursorApplied)(using locatedCtx) val autoImportsGen = AutoImports.generator( completionPos.toSourcePosition, @@ -126,23 +168,30 @@ class CompletionProvider( * Otherwise, completion poisition doesn't point at any tree * because scala parser trim end position to the last statement pos. */ - private def applyCompletionCursor(params: OffsetParams): String = + private def applyCompletionCursor(params: OffsetParams): (Boolean, String) = val text = params.text().nn val offset = params.offset().nn + val query = Completion.naiveCompletionPrefix(text, offset) - val isStartMultilineComment = - val i = params.offset() - i >= 3 && (text.charAt(i - 1) match - case '*' => - text.charAt(i - 2) == '*' && - text.charAt(i - 3) == '/' - case _ => false - ) - if isStartMultilineComment then - // Insert potentially missing `*/` to avoid comment out all codes after the "/**". - text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset) + if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart + && !CompletionProvider.allKeywords.contains(query) then false -> text else - text.substring(0, offset).nn + Cursor.value + text.substring(offset) + val isStartMultilineComment = + + val i = params.offset() + i >= 3 && (text.charAt(i - 1) match + case '*' => + text.charAt(i - 2) == '*' && + text.charAt(i - 3) == '/' + case _ => false + ) + true -> ( + if isStartMultilineComment then + // Insert potentially missing `*/` to avoid comment out all codes after the "/**". + text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset) + else + text.substring(0, offset).nn + Cursor.value + text.substring(offset) + ) end applyCompletionCursor private def completionItems( @@ -175,7 +224,7 @@ class CompletionProvider( Select(Apply(Select(Select(_, name), _), _), _), _ ) :: _ => - name == StdNames.nme.StringContext + name == nme.StringContext // "My name is $name" case Literal(Constant(_: String)) :: _ => true diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 3bebaa76a309..05dbe1ef5a43 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -5,7 +5,6 @@ import java.nio.file.Path import java.nio.file.Paths import scala.collection.mutable -import scala.meta.internal.metals.Fuzzy import scala.meta.internal.metals.ReportContext import scala.meta.internal.mtags.CoursierComplete import scala.meta.internal.pc.{IdentifierComparator, MemberOrdering, CompletionFuzzy} @@ -27,15 +26,12 @@ import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.interactive.Completion import dotty.tools.dotc.interactive.Completion.Mode -import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.util.SrcPos import dotty.tools.pc.AutoImports.AutoImportsGenerator import dotty.tools.pc.buildinfo.BuildInfo import dotty.tools.pc.completions.OverrideCompletions.OverrideExtractor import dotty.tools.pc.utils.InteractiveEnrichments.* -import dotty.tools.dotc.core.Denotations.SingleDenotation -import dotty.tools.dotc.interactive.Interactive class Completions( text: String, @@ -67,23 +63,19 @@ class Completions( case _ :: (_: UnApply) :: _ => false case _ => true - private lazy val shouldAddSuffix = shouldAddSnippet && + private lazy val shouldAddSuffix = shouldAddSnippet && (path match /* In case of `method@@()` we should not add snippets and the path * will contain apply as the parent of the current tree. */ - case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun => - false + case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun => false /* In case of `T@@[]` we should not add snippets. */ - case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => - false - case _ :: (withcursor @ Select(fun, name)) :: (appl: GenericApply) :: _ - if appl.fun == withcursor && name.decoded == Cursor.value => - false + case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => false + case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _ + if appl.fun == funSel && sel == fun => false case _ => true) - private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath) def includeSymbol(sym: Symbol)(using Context): Boolean = @@ -283,7 +275,6 @@ class Completions( val affix = if methodDenot.symbol.isConstructor && existsApply then adjustedPath match case (select @ Select(qual, _)) :: _ => - val start = qual.span.start val insertRange = select.sourcePos.startPos.withEnd(completionPos.queryEnd).toLsp suffix @@ -521,14 +512,8 @@ class Completions( if tree.selectors.exists(_.renamed.sourcePos.contains(pos)) => (List.empty, true) - // From Scala 3.1.3-RC3 (as far as I know), path contains - // `Literal(Constant(null))` on head for an incomplete program, in this case, just ignore the head. - case Literal(Constant(null)) :: tl => - advancedCompletions(tl, completionPos) - case _ => val args = NamedArgCompletions.contribute( - pos, path, adjustedPath, indexedContext, @@ -672,7 +657,7 @@ class Completions( .collect { case symbolic: CompletionValue.Symbolic => symbolic } .groupBy(_.symbol.fullName) // we somehow have to ignore proxy type - val filteredSymbolicCompletions = symbolicCompletionsMap.filter: (name, denots) => + val filteredSymbolicCompletions = symbolicCompletionsMap.filter: (name, _) => lazy val existsTypeWithoutSuffix: Boolean = !symbolicCompletionsMap .get(name.toTypeName) .forall(_.forall(sym => sym.snippetAffix.suffixes.nonEmpty)) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala index 2e39c17b24b3..da46e5167834 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala @@ -224,7 +224,7 @@ object InterpolatorCompletions: buildTargetIdentifier: String )(using ctx: Context, reportsContext: ReportContext): List[CompletionValue] = val litStartPos = lit.span.start - val litEndPos = lit.span.end - Cursor.value.length() + val litEndPos = lit.span.end - (if completionPos.withCURSOR then Cursor.value.length else 0) val position = completionPos.originalCursorPosition val span = position.span val nameStart = diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala index 647b151a635b..dd3a910beb4f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala @@ -27,7 +27,6 @@ import dotty.tools.dotc.core.Types.TermRef import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.core.Types.TypeBounds import dotty.tools.dotc.core.Types.WildcardType -import dotty.tools.dotc.util.SourcePosition import dotty.tools.pc.IndexedContext import dotty.tools.pc.utils.InteractiveEnrichments.* import scala.annotation.tailrec @@ -35,9 +34,8 @@ import scala.annotation.tailrec object NamedArgCompletions: def contribute( - pos: SourcePosition, path: List[Tree], - untypedPath: => List[untpd.Tree], + untypedPath: List[untpd.Tree], indexedContext: IndexedContext, clientSupportsSnippets: Boolean, )(using ctx: Context): List[CompletionValue] = @@ -64,12 +62,13 @@ object NamedArgCompletions: for app <- getApplyForContextFunctionParam(rest) if !app.fun.isInfix - yield contribute( - Some(ident), - app, - indexedContext, - clientSupportsSnippets, - ) + yield + contribute( + Some(ident), + app, + indexedContext, + clientSupportsSnippets, + ) contribution.getOrElse(Nil) case (app: Apply) :: _ => /** @@ -156,10 +155,11 @@ object NamedArgCompletions: case _ => None val matchingMethods = for - (name, indxContext) <- maybeNameAndIndexedContext(method) - potentialMatches <- indxContext.findSymbol(name) - yield potentialMatches.collect { - case m + (name, indexedContext) <- maybeNameAndIndexedContext(method) + potentialMatches <- indexedContext.findSymbol(name) + yield + potentialMatches.collect { + case m if m.is(Flags.Method) && m.vparamss.length >= argss.length && Try(m.isAccessibleFrom(apply.symbol.info)).toOption @@ -170,7 +170,7 @@ object NamedArgCompletions: .zipWithIndex .forall { case (pair, index) => FuzzyArgMatcher(m.tparams) - .doMatch(allArgsProvided = index != 0) + .doMatch(allArgsProvided = index != 0, ident) .tupled(pair) } => m @@ -179,8 +179,7 @@ object NamedArgCompletions: end fallbackFindMatchingMethods val matchingMethods: List[Symbols.Symbol] = - if method.symbol.paramSymss.nonEmpty - then + if method.symbol.paramSymss.nonEmpty then val allArgsAreSupplied = val vparamss = method.symbol.vparamss vparamss.length == argss.length && vparamss @@ -386,12 +385,13 @@ class FuzzyArgMatcher(tparams: List[Symbols.Symbol])(using Context): * We check the args types not the result type. */ def doMatch( - allArgsProvided: Boolean + allArgsProvided: Boolean, + ident: Option[Ident] )(expectedArgs: List[Symbols.Symbol], actualArgs: List[Tree]) = (expectedArgs.length == actualArgs.length || (!allArgsProvided && expectedArgs.length >= actualArgs.length)) && actualArgs.zipWithIndex.forall { - case (Ident(name), _) if name.endsWith(Cursor.value) => true + case (arg: Ident, _) if ident.contains(arg) => true case (NamedArg(name, arg), _) => expectedArgs.exists { expected => expected.name == name && (!arg.hasType || arg.typeOpt.unfold diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala index 1e310ca0e8ec..f5c15ca6df0e 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala @@ -530,8 +530,11 @@ object OverrideCompletions: object OverrideExtractor: def unapply(path: List[Tree])(using Context) = path match - // class FooImpl extends Foo: - // def x| + // abstract class Val: + // def hello: Int = 2 + // + // class Main extends Val: + // def h| case (dd: (DefDef | ValDef)) :: (t: Template) :: (td: TypeDef) :: _ if t.parents.nonEmpty => val completing = @@ -547,12 +550,13 @@ object OverrideCompletions: ) ) - // class FooImpl extends Foo: + // abstract class Val: + // def hello: Int = 2 + // + // class Main extends Val: // ov| case (ident: Ident) :: (t: Template) :: (td: TypeDef) :: _ - if t.parents.nonEmpty && "override".startsWith( - ident.name.show.replace(Cursor.value, "") - ) => + if t.parents.nonEmpty && "override".startsWith(ident.name.show.replace(Cursor.value, "")) => Some( ( td, @@ -563,15 +567,13 @@ object OverrideCompletions: ) ) + // abstract class Val: + // def hello: Int = 2 + // // class Main extends Val: // def@@ case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _ - if t.parents.nonEmpty && "def".startsWith( - id.name.decoded.replace( - Cursor.value, - "", - ) - ) => + if t.parents.nonEmpty && "def".startsWith(id.name.decoded.replace(Cursor.value, "")) => Some( ( td, @@ -581,8 +583,12 @@ object OverrideCompletions: None, ) ) + + // abstract class Val: + // def hello: Int = 2 + // // class Main extends Val: - // he@@ + // he@@ case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _ if t.parents.nonEmpty => Some( @@ -595,6 +601,23 @@ object OverrideCompletions: ) ) + // abstract class Val: + // def hello: Int = 2 + // + // class Main extends Val: + // hello@ // this transforms into this.hello, thus is a Select + case (sel @ Select(th: This, name)) :: (t: Template) :: (td: TypeDef) :: _ + if t.parents.nonEmpty && th.qual.name == td.name => + Some( + ( + td, + None, + sel.sourcePos.start, + false, + Some(name.show), + ) + ) + case _ => None end OverrideExtractor diff --git a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala index a0dcb5276253..a738440c585d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala @@ -24,8 +24,6 @@ import dotty.tools.dotc.printing.RefinedPrinter import dotty.tools.dotc.printing.Texts.Text import dotty.tools.pc.AutoImports.AutoImportsGenerator import dotty.tools.pc.AutoImports.ImportSel -import dotty.tools.pc.AutoImports.ImportSel.Direct -import dotty.tools.pc.AutoImports.ImportSel.Rename import dotty.tools.pc.IndexedContext import dotty.tools.pc.IndexedContext.Result import dotty.tools.pc.Params diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala index 78635e540c43..7d29e6c4dda9 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala @@ -8,9 +8,7 @@ import scala.meta.internal.metals.CompilerRangeParams import scala.language.unsafeNulls import dotty.tools.pc.utils.TestInlayHints -import dotty.tools.pc.utils.TextEdits -import org.eclipse.lsp4j.TextEdit class BaseInlayHintsSuite extends BasePCSuite { @@ -55,4 +53,4 @@ class BaseInlayHintsSuite extends BasePCSuite { obtained, ) -} \ No newline at end of file +} diff --git a/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala b/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala index 82e697e6e9a1..4999e0ddbc69 100644 --- a/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala +++ b/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala @@ -13,22 +13,17 @@ class ReusableClassRunner(testClass: Class[BasePCSuite]) testClass.getDeclaredConstructor().newInstance() override def createTest(): AnyRef = instance - override def withBefores( - method: FrameworkMethod, - target: Object, - statement: Statement - ): Statement = - statement override def withAfters( method: FrameworkMethod, target: Object, statement: Statement ): Statement = + val newStatement = super.withAfters(method, target, statement) new Statement(): override def evaluate(): Unit = try - statement.evaluate() + newStatement.evaluate() finally if (isLastTestCase(method)) then instance.clean() diff --git a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala new file mode 100644 index 000000000000..5e13c07b9e5f --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala @@ -0,0 +1,163 @@ +package dotty.tools.pc.tests + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.pc.base.BasePCSuite +import dotty.tools.pc.ScalaPresentationCompiler +import org.junit.{Before, Test} + +import scala.language.unsafeNulls +import scala.meta.internal.metals.EmptyCancelToken +import scala.meta.internal.metals.CompilerOffsetParams +import scala.meta.pc.OffsetParams +import scala.concurrent.Future +import scala.concurrent.Await +import scala.meta.pc.VirtualFileParams +import scala.concurrent.duration.* + +import java.util.Collections +import java.nio.file.Paths +import java.util.concurrent.CompletableFuture + + +class CompilerCachingSuite extends BasePCSuite: + + val timeout = 5.seconds + + private def checkCompilationCount(expected: Int): Unit = + presentationCompiler match + case pc: ScalaPresentationCompiler => + val compilations = pc.compilerAccess.withNonInterruptableCompiler(None)(-1, EmptyCancelToken) { driver => + driver.compiler().currentCtx.runId + }.get(timeout.length, timeout.unit) + assertEquals(expected, compilations, s"Expected $expected compilations but got $compilations") + case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler") + + private def getContext(): Context = + presentationCompiler match + case pc: ScalaPresentationCompiler => + pc.compilerAccess.withNonInterruptableCompiler(None)(null, EmptyCancelToken) { driver => + driver.compiler().currentCtx + }.get(timeout.length, timeout.unit) + case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler") + + @Before + def beforeEach: Unit = + presentationCompiler.restart() + + // We want to run at least one compilation, so runId points at 3. + // This will ensure that we use the same driver, not recreate fresh one on each call + val dryRunParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "dryRun", 1, EmptyCancelToken) + checkCompilationCount(2) + val freshContext = getContext() + presentationCompiler.complete(dryRunParams).get(timeout.length, timeout.unit) + checkCompilationCount(3) + val dryRunContext = getContext() + assert(freshContext != dryRunContext) + + + @Test + def `cursor-compilation-does-not-corrupt-cache`: Unit = + val contextPreCompilation = getContext() + + val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken) + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + val contextPostFirst = getContext() + assert(contextPreCompilation != contextPostFirst) + checkCompilationCount(4) + + val fakeParamsCursor = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = new", 15, EmptyCancelToken) + presentationCompiler.complete(fakeParamsCursor).get(timeout.length, timeout.unit) + val contextPostCursor = getContext() + assert(contextPreCompilation != contextPostCursor) + assert(contextPostFirst == contextPostCursor) + checkCompilationCount(4) + + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + val contextPostSecond = getContext() + assert(contextPreCompilation != contextPostSecond) + assert(contextPostFirst == contextPostCursor) + assert(contextPostCursor == contextPostSecond) + checkCompilationCount(4) + + @Test + def `compilation-for-same-snippet-is-cached`: Unit = + val contextPreCompilation = getContext() + + val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken) + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + val contextPostFirst = getContext() + assert(contextPreCompilation != contextPostFirst) + checkCompilationCount(4) + + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + val contextPostSecond = getContext() + assert(contextPreCompilation != contextPostFirst) + assert(contextPostSecond == contextPostFirst) + checkCompilationCount(4) + + @Test + def `compilation-for-different-snippet-is-not-cached`: Unit = + + + checkCompilationCount(3) + val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = prin", 16, EmptyCancelToken) + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + checkCompilationCount(4) + + val fakeParams2 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = prin", 16, EmptyCancelToken) + presentationCompiler.complete(fakeParams2).get(timeout.length, timeout.unit) + checkCompilationCount(5) + + val fakeParams3 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = print", 17, EmptyCancelToken) + presentationCompiler.complete(fakeParams3).get(timeout.length, timeout.unit) + checkCompilationCount(6) + + + private val testFunctions: List[OffsetParams => CompletableFuture[_]] = List( + presentationCompiler.complete(_), + presentationCompiler.convertToNamedArguments(_, Collections.emptyList()), + presentationCompiler.autoImports("a", _, false), + presentationCompiler.definition(_), + presentationCompiler.didChange(_), + presentationCompiler.documentHighlight(_), + presentationCompiler.hover(_), + presentationCompiler.implementAbstractMembers(_), + presentationCompiler.insertInferredType(_), + presentationCompiler.semanticTokens(_), + presentationCompiler.prepareRename(_), + presentationCompiler.rename(_, "a"), + presentationCompiler.signatureHelp(_), + presentationCompiler.typeDefinition(_) + ) + + + @Test + def `different-api-calls-reuse-cache`: Unit = + val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 13, EmptyCancelToken) + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + + val contextBefore = getContext() + + val differentContexts = testFunctions.map: f => + f(fakeParams).get(timeout.length, timeout.unit) + checkCompilationCount(4) + getContext() + .toSet + + assert(differentContexts == Set(contextBefore)) + + @Test + def `different-api-calls-reuse-cache-parallel`: Unit = + import scala.jdk.FutureConverters.* + import scala.concurrent.ExecutionContext.Implicits.global + + val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 13, EmptyCancelToken) + presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit) + + val contextBefore = getContext() + + val futures = testFunctions.map: f => + f(fakeParams).asScala.map(_ => getContext()) + + val res = Await.result(Future.sequence(futures), timeout).toSet + assert(res == Set(contextBefore)) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala index 210a28f6a7a1..17f21b16d6e8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala @@ -614,8 +614,9 @@ class CompletionArgSuite extends BaseCompletionSuite: check( s"""|case class Context() | - |def foo(arg1: (Context) ?=> Int, arg2: Int): String = ??? - |val m = foo(ar@@) + |object Main: + | def foo(arg1: (Context) ?=> Int, arg2: Int): String = ??? + | val m = foo(ar@@) |""".stripMargin, """|arg1 = : (Context) ?=> Int |arg2 = : Int @@ -627,8 +628,9 @@ class CompletionArgSuite extends BaseCompletionSuite: check( s"""|case class Context() | - |def foo(arg1: Context ?=> Int, arg2: Context ?=> Int): String = ??? - |val m = foo(arg1 = ???, a@@) + |object Main: + | def foo(arg1: Context ?=> Int, arg2: Context ?=> Int): String = ??? + | val m = foo(arg1 = ???, a@@) |""".stripMargin, """|arg2 = : (Context) ?=> Int |""".stripMargin, @@ -639,8 +641,9 @@ class CompletionArgSuite extends BaseCompletionSuite: check( s"""|case class Context() | - |def foo(arg1: (Boolean, Context) ?=> Int ?=> String, arg2: (Boolean, Context) ?=> Int ?=> String): String = ??? - |val m = foo(arg1 = ???, a@@) + |object Main: + | def foo(arg1: (Boolean, Context) ?=> Int ?=> String, arg2: (Boolean, Context) ?=> Int ?=> String): String = ??? + | val m = foo(arg1 = ???, a@@) |""".stripMargin, """|arg2 = : (Boolean, Context) ?=> (Int) ?=> String |""".stripMargin, @@ -786,10 +789,11 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `overloaded-with-param` = check( - """|def m(idd : String, abb: Int): Int = ??? - |def m(inn : Int, uuu: Option[Int]): Int = ??? - |def m(inn : Int, aaa: Int): Int = ??? - |def k: Int = m(1, a@@) + """|object Main: + | def m(idd : String, abb: Int): Int = ??? + | def m(inn : Int, uuu: Option[Int]): Int = ??? + | def m(inn : Int, aaa: Int): Int = ??? + | def k: Int = m(1, a@@) |""".stripMargin, """|aaa = : Int |assert(assertion: Boolean): Unit @@ -799,10 +803,11 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `overloaded-with-named-param` = check( - """|def m(idd : String, abb: Int): Int = ??? - |def m(inn : Int, uuu: Option[Int]): Int = ??? - |def m(inn : Int, aaa: Int): Int = ??? - |def k: Int = m(inn = 1, a@@) + """|object Main: + | def m(idd : String, abb: Int): Int = ??? + | def m(inn : Int, uuu: Option[Int]): Int = ??? + | def m(inn : Int, aaa: Int): Int = ??? + | def k: Int = m(inn = 1, a@@) |""".stripMargin, """|aaa = : Int |assert(assertion: Boolean): Unit @@ -812,7 +817,7 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `overloaded-generic` = check( - """|object M: + """|object Main: | val g = 3 | val l : List[Int] = List(1,2,3) | def m[T](inn : List[T], yy: Int, aaa: Int, abb: Option[Int]): Int = ??? @@ -899,10 +904,11 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `overloaded-function-param` = check( - """|def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ??? - |def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ??? - |def m[T](i: Int)(inn: T => String, acc: List[Int]): Int = ??? - |def k = m(1)(inn = identity[Int], a@@) + """|object Main: + | def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ??? + | def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ??? + | def m[T](i: Int)(inn: T => String, acc: List[Int]): Int = ??? + | def k = m(1)(inn = identity[Int], a@@) |""".stripMargin, """|aaa = : Int |abb = : Option[Int] @@ -913,10 +919,11 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `overloaded-function-param2` = check( - """|def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ??? - |def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ??? - |def m[T](i: String)(inn: T => Int, acc: List[Int]): Int = ??? - |def k = m(1)(inn = identity[Int], a@@) + """|object Main: + | def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ??? + | def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ??? + | def m[T](i: String)(inn: T => Int, acc: List[Int]): Int = ??? + | def k = m(1)(inn = identity[Int], a@@) |""".stripMargin, """|aaa = : Int |abb = : Option[Int] @@ -978,9 +985,10 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `overloaded-function-param3` = check( - """|def m[T](inn: Int => T, abb: Option[Int]): Int = ??? - |def m[T](inn: String => T, aaa: Int): Int = ??? - |def k = m(identity[Int], a@@) + """|object Main: + | def m[T](inn: Int => T, abb: Option[Int]): Int = ??? + | def m[T](inn: String => T, aaa: Int): Int = ??? + | def k = m(identity[Int], a@@) |""".stripMargin, """|abb = : Option[Int] |""".stripMargin, @@ -1109,7 +1117,7 @@ class CompletionArgSuite extends BaseCompletionSuite: @Test def `comparison` = check( - """package a + """ |object w { | abstract class T(x: Int) { | def met(x: Int): Unit = { diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala index 010d0b14fa90..6a8759d0a0c9 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala @@ -1,14 +1,10 @@ package dotty.tools.pc.tests.completion -import scala.meta.pc.SymbolDocumentation import scala.language.unsafeNulls import dotty.tools.pc.base.BaseCompletionSuite -import dotty.tools.pc.utils.MockEntries import org.junit.Test -import org.junit.Ignore -import scala.collection.immutable.ListMapBuilder class CompletionExtraConstructorSuite extends BaseCompletionSuite: diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala index 08cc1535fd56..50019928a2f3 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala @@ -112,7 +112,7 @@ class CompletionInterpolatorSuite extends BaseCompletionSuite: |""".stripMargin.triplequoted, """|object Main { | val myName = "" - | s"$myName $$" + | s"$myName$0 $$" |} |""".stripMargin.triplequoted, filterText = "myName" diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 47e4cabb76f4..1cd26858b934 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -530,8 +530,6 @@ class CompletionSuite extends BaseCompletionSuite: """.stripMargin, """|until(end: Int): Range |until(end: Int, step: Int): Range - |until(end: Long): Exclusive[Long] - |until(end: Long, step: Long): Exclusive[Long] |""".stripMargin, stableOrder = false ) @@ -1606,7 +1604,7 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `multi-export` = check( - """export scala.collection.{AbstractMap, Set@@} + """export scala.collection.{AbstractMap, Se@@} |""".stripMargin, """Set scala.collection |SetOps scala.collection @@ -1619,7 +1617,9 @@ class CompletionSuite extends BaseCompletionSuite: |StrictOptimizedSetOps scala.collection |StrictOptimizedSortedSetOps scala.collection |GenSet = scala.collection.Set[X] - |""".stripMargin + |""".stripMargin, + filter = _.contains("Set") + ) @Test def `multi-imports` = @@ -1638,6 +1638,7 @@ class CompletionSuite extends BaseCompletionSuite: |StrictOptimizedSortedSetOps scala.collection |GenSet = scala.collection.Set[X] |""".stripMargin, + filter = _.contains("Set") ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala index c7c9b9979404..fab21ffdee0a 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala @@ -28,7 +28,7 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: MockLocation("scala/Predef.Ensuring#ensuring(+2).", "Predef.scala"), MockLocation("scala/Predef.Ensuring#ensuring(+3).", "Predef.scala"), MockLocation("scala/collection/immutable/List#`::`().", "List.scala"), - MockLocation("scala/collection/IterableFactory#apply().", "Factory.scala") + MockLocation("scala/package.List.", "package.scala") ) override def definitions(offsetParams: OffsetParams): List[Location] = @@ -123,7 +123,7 @@ class PcDefinitionSuite extends BasePcDefinitionSuite: check( """| |object Main { - | /*scala/collection/IterableFactory#apply(). Factory.scala*/@@List(1) + | /*scala/package.List. package.scala*/@@List(1) |} |""".stripMargin ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala index 9ae37048caf7..3e7a2549cbe0 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala @@ -269,9 +269,9 @@ class HoverTermSuite extends BaseHoverSuite: | } yield x |} |""".stripMargin, - """|Option[Int] - |override def headOption: Option[A] - |""".stripMargin.hover + """|```scala + |override def headOption: Option[Int] + |```""".stripMargin.hover ) @Test def `object` = @@ -694,3 +694,11 @@ class HoverTermSuite extends BaseHoverSuite: |""".stripMargin, "extension [T](a: T) def *:[U <: Tuple](b: Wrap[U]): Wrap[T *: U]".hover ) + + @Test def `dont-ignore-???-in-path`: Unit = + check( + """object Obj: + | val x = ?@@?? + |""".stripMargin, + """def ???: Nothing""".stripMargin.hover + ) diff --git a/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala b/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala index a37801b3c48c..3dabcded4e45 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala @@ -3,7 +3,7 @@ package dotty.tools.pc.utils import scala.meta.pc.VirtualFileParams import dotty.tools.dotc.ast.tpd.* -import dotty.tools.dotc.ast.{Trees, tpd} +import dotty.tools.dotc.ast.Trees import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourcePosition diff --git a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala index 9015a39ba9e7..459c41e3c8e5 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala @@ -8,7 +8,6 @@ import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* import scala.meta.internal.metals.{ClasspathSearch, WorkspaceSymbolQuery} import scala.meta.pc.ContentType -import scala.meta.pc.SymbolSearch.Result import scala.meta.pc.{ ParentSymbols, SymbolDocumentation, diff --git a/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala b/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala index ef15121c6702..af4502d66b4b 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala @@ -4,7 +4,6 @@ import scala.language.unsafeNulls import dotty.tools.pc.completions.CompletionSource import dotty.tools.dotc.util.DiffUtil -import dotty.tools.pc.utils.InteractiveEnrichments.* import org.hamcrest import org.hamcrest.* @@ -127,7 +126,6 @@ trait PcAssertions: def getDetailedMessage(diff: String): String = val lines = diff.linesIterator.toList val sources = completionSources.padTo(lines.size, CompletionSource.Empty) - val maxLength = lines.map(_.length).maxOption.getOrElse(0) var completionIndex = 0 lines.map: line => if line.startsWith(Console.BOLD + Console.RED) || line.startsWith(" ") then diff --git a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala index a923b76b955c..b9d3fd411dcc 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala @@ -4,7 +4,6 @@ import scala.collection.mutable.ListBuffer import scala.meta.internal.jdk.CollectionConverters._ import dotty.tools.pc.utils.InteractiveEnrichments.* -import dotty.tools.pc.utils.TextEdits import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.TextEdit @@ -67,4 +66,4 @@ object TestInlayHints { def removeInlayHints(text: String): String = text.replaceAll(raw"\/\*(.*?)\*\/", "").nn -} \ No newline at end of file +}