From 857c6c8bfc008e7b7073e099ec97fa3865402a86 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 15 Jul 2024 17:07:41 +0200 Subject: [PATCH 1/6] fix: show zero extent references when using pc --- .../src/main/dotty/tools/pc/PcCollector.scala | 12 ++- .../dotty/tools/pc/PcReferencesProvider.scala | 4 +- .../tools/pc/tests/PcReferencesSuite.scala | 86 +++++++++++++++++++ .../dotty/tools/pc/utils/RangeReplace.scala | 19 ++-- 4 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala index 5de80cda4ddf..1ebfd405768e 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala @@ -35,6 +35,8 @@ trait PcCollector[T]: parent: Option[Tree] )(tree: Tree| EndMarker, pos: SourcePosition, symbol: Option[Symbol]): T + def allowZeroExtentImplicits: Boolean = false + def resultAllOccurences(): Set[T] = def noTreeFilter = (_: Tree) => true def noSoughtFilter = (_: Symbol => Boolean) => true @@ -87,6 +89,10 @@ trait PcCollector[T]: def isCorrect = !span.isZeroExtent && span.exists && span.start < sourceText.size && span.end <= sourceText.size + extension (tree: Tree) + def isCorrectSpan = + tree.span.isCorrect || (allowZeroExtentImplicits && tree.symbol.is(Flags.Implicit)) + def traverseSought( filter: Tree => Boolean, soughtFilter: (Symbol => Boolean) => Boolean @@ -107,7 +113,7 @@ trait PcCollector[T]: * All indentifiers such as: * val a = <> */ - case ident: Ident if ident.span.isCorrect && filter(ident) => + case ident: Ident if ident.isCorrectSpan && filter(ident) => // symbols will differ for params in different ext methods, but source pos will be the same if soughtFilter(_.sourcePos == ident.symbol.sourcePos) then @@ -122,7 +128,7 @@ trait PcCollector[T]: * val x = new <>(1) */ case sel @ Select(New(t), _) - if sel.span.isCorrect && + if sel.isCorrectSpan && sel.symbol.isConstructor && t.symbol == NoSymbol => if soughtFilter(_ == sel.symbol.owner) then @@ -137,7 +143,7 @@ trait PcCollector[T]: * val a = hello.<> */ case sel: Select - if sel.span.isCorrect && filter(sel) && + if sel.isCorrectSpan && filter(sel) && !sel.isForComprehensionMethod => occurrences + collect( sel, diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcReferencesProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcReferencesProvider.scala index 8d22ce320eee..49ed313faec4 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcReferencesProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcReferencesProvider.scala @@ -23,6 +23,8 @@ class PcReferencesProvider( request: ReferencesRequest, ) extends WithCompilationUnit(driver, request.file()) with PcCollector[Option[(String, Option[lsp4j.Range])]]: + override def allowZeroExtentImplicits: Boolean = true + private def soughtSymbols = if(request.offsetOrSymbol().isLeft()) { val offsetParams = CompilerOffsetParams( @@ -64,4 +66,4 @@ class PcReferencesProvider( } .toList case _ => Nil -end PcReferencesProvider \ No newline at end of file +end PcReferencesProvider diff --git a/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala new file mode 100644 index 000000000000..aee3fd37617f --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala @@ -0,0 +1,86 @@ +package dotty.tools.pc.tests +import dotty.tools.pc.base.BasePCSuite +import dotty.tools.pc.utils.RangeReplace +import scala.meta.internal.pc.PcReferencesRequest +import scala.meta.internal.metals.CompilerVirtualFileParams +import java.net.URI +import scala.meta.internal.metals.EmptyCancelToken +import org.eclipse.lsp4j.jsonrpc.messages.{Either => JEither} +import scala.meta.internal.jdk.CollectionConverters.* + +import org.junit.Test + +class PcReferencesSuite extends BasePCSuite with RangeReplace { + def check( + original: String, + ): Unit = + val edit = original.replaceAll("(<<|>>)", "") + val expected = original.replaceAll("@@", "") + val base = original.replaceAll("(<<|>>|@@)", "") + + val (code, offset) = params(edit, "Highlight.scala") + val ranges = presentationCompiler + .references( + PcReferencesRequest( + CompilerVirtualFileParams( + URI.create("file:/Highlight.scala"), + code, + EmptyCancelToken + ), + includeDefinition = false, + offsetOrSymbol = JEither.forLeft(offset) + ) + ) + .get() + .asScala + .flatMap(_.locations().asScala.map(_.getRange())) + .toList + + assertEquals( + renderRangesAsString(base, ranges), + expected, + "references should match" + ) + + @Test def `implicit-args` = + check( + """|package example + | + |class Bar(i: Int) + | + |object Hello { + | def m(i: Int)(implicit b: Bar) = ??? + | val foo = { + | implicit val ba@@rr: Bar = new Bar(1) + | m(3)<<>> + | } + |} + |""".stripMargin + ) + + @Test def `implicit-args-2` = + check( + """|package example + | + |class Bar(i: Int) + |class Foo(implicit b: Bar) + | + |object Hello { + | implicit val ba@@rr: Bar = new Bar(1) + | val foo = new Foo<<>> + |} + |""".stripMargin + ) + + @Test def `case-class` = + check( + """|case class Ma@@in(i: Int) + |""".stripMargin + ) + + @Test def `case-class-with-implicit` = + check( + """"|case class A()(implicit val fo@@o: Int) + |""".stripMargin + ) +} diff --git a/presentation-compiler/test/dotty/tools/pc/utils/RangeReplace.scala b/presentation-compiler/test/dotty/tools/pc/utils/RangeReplace.scala index 0b41b106eb02..deafad4987ce 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/RangeReplace.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/RangeReplace.scala @@ -12,14 +12,21 @@ trait RangeReplace: def renderHighlightsAsString( code: String, highlights: List[DocumentHighlight] + ): String = renderRangesAsString(code, highlights.map(_.getRange())) + + def renderRangesAsString( + code: String, + highlights: List[Range], + alreadyAddedMarkings: List[(Int, Int)] = Nil, + currentBase: Option[String] = None ): String = highlights - .foldLeft((code, immutable.List.empty[(Int, Int)])) { - case ((base, alreadyAddedMarkings), location) => - replaceInRangeWithAdjustmens( + .foldLeft((currentBase.getOrElse(code), alreadyAddedMarkings)) { + case ((base, alreadyAddedMarkings), range) => + replaceInRangeWithAdjustments( code, base, - location.getRange, + range, alreadyAddedMarkings ) } @@ -31,9 +38,9 @@ trait RangeReplace: prefix: String = "<<", suffix: String = ">>" ): String = - replaceInRangeWithAdjustmens(base, base, range, List(), prefix, suffix)._1 + replaceInRangeWithAdjustments(base, base, range, List(), prefix, suffix)._1 - protected def replaceInRangeWithAdjustmens( + protected def replaceInRangeWithAdjustments( code: String, currentBase: String, range: Range, From d2c6d6049efda132a27874dab1414d6d26fecfa6 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 15 Jul 2024 17:10:10 +0200 Subject: [PATCH 2/6] fix: use already imported package aliases for auto import --- .../src/main/dotty/tools/pc/AutoImports.scala | 9 ++++++++- .../tools/pc/tests/edit/AutoImportsSuite.scala | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala index bf814ef682e0..896954c4e1a4 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala @@ -269,7 +269,14 @@ object AutoImports: private def importName(sym: Symbol): String = if indexedContext.importContext.toplevelClashes(sym) then s"_root_.${sym.fullNameBackticked(false)}" - else sym.fullNameBackticked(false) + else + sym.ownersIterator.zipWithIndex.foldLeft((List.empty[String], false)) { case ((acc, isDone), (sym, idx)) => + if(isDone || sym.isEmptyPackage || sym.isRoot) (acc, true) + else indexedContext.rename(sym) match + case Some(renamed) => (renamed :: acc, true) + case None if !sym.isPackageObject => (sym.nameBackticked(false) :: acc, false) + case None => (acc, false) + }._1.mkString(".") end AutoImportsGenerator private def autoImportPosition( diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala index ce5ae4a1cca4..e4ef8c0f747d 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala @@ -436,6 +436,19 @@ class AutoImportsSuite extends BaseAutoImportsSuite: |""".stripMargin ) + @Test def `use-packages-in-scope` = + checkEdit( + """|import scala.collection.mutable as mut + | + |val l = <>(2) + |""".stripMargin, + """|import scala.collection.mutable as mut + |import mut.ListBuffer + | + |val l = ListBuffer(2) + |""".stripMargin + ) + private def ammoniteWrapper(code: String): String = // Vaguely looks like a scala file that Ammonite generates // from a sc file. From 2cfc5619b5b5089527d56b11e5189918f881bcfe Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 15 Jul 2024 17:26:25 +0200 Subject: [PATCH 3/6] improvement: add info needed for running tests to symbol info --- .../tools/pc/SymbolInformationProvider.scala | 18 ++++++++++++++++++ project/Build.scala | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala index 18d6a4ec8621..da075e21f486 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala @@ -1,5 +1,6 @@ package dotty.tools.pc +import scala.collection.mutable import scala.util.control.NonFatal import scala.meta.pc.PcSymbolKind @@ -37,11 +38,25 @@ class SymbolInformationProvider(using Context): if classSym.isClass then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName) else Nil + val allParents = + val visited = mutable.Set[Symbol]() + def collect(sym: Symbol): Unit = { + visited += sym + if sym.isClass + then sym.asClass.parentSyms.foreach { + case parent if !visited(parent) => + collect(parent) + case _ => + } + } + collect(classSym) + visited.toList.map(SemanticdbSymbols.symbolName) val dealisedSymbol = if sym.isAliasType then sym.info.deepDealias.typeSymbol else sym val classOwner = sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module)) val overridden = sym.denot.allOverriddenSymbols.toList + val memberDefAnnots = sym.info.membersBasedOnFlags(Flags.Method, Flags.EmptyFlags).flatMap(_.allSymbols).flatMap(_.denot.annotations) val pcSymbolInformation = PcSymbolInformation( @@ -56,6 +71,9 @@ class SymbolInformationProvider(using Context): properties = if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT) else Nil, + recursiveParents = allParents, + annotations = sym.denot.annotations.map(_.symbol.showFullName), + memberDefsAnnotations = memberDefAnnots.map(_.symbol.showFullName).toList ) Some(pcSymbolInformation) diff --git a/project/Build.scala b/project/Build.scala index 86b0b0d50c03..8c9a0e69f0ef 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1359,7 +1359,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.3.2" + val mtagsVersion = "1.3.3" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0", From 3b3b8a77ef911825bfb0d5dc5406b641332aaa07 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 15 Jul 2024 18:17:25 +0200 Subject: [PATCH 4/6] feat: add value completions for union types --- .../pc/completions/CompletionValue.scala | 9 + .../tools/pc/completions/Completions.scala | 53 +--- .../pc/completions/SingletonCompletions.scala | 133 ++++++++ .../SingletonCompletionsSuite.scala | 300 ++++++++++++++++++ 4 files changed, 459 insertions(+), 36 deletions(-) create mode 100644 presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala create mode 100644 presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionValue.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionValue.scala index 9071b2cd2a23..98cceae149d3 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionValue.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionValue.scala @@ -354,6 +354,15 @@ object CompletionValue: description override def insertMode: Option[InsertTextMode] = Some(InsertTextMode.AsIs) + case class SingletonValue(label: String, info: Type, override val range: Option[Range]) + extends CompletionValue: + override def insertText: Option[String] = Some(label) + override def labelWithDescription(printer: ShortenedTypePrinter)(using Context): String = + s"$label: ${printer.tpe(info)}" + + override def completionItemKind(using Context): CompletionItemKind = + CompletionItemKind.Constant + def namedArg(label: String, sym: ParamSymbol)(using Context ): CompletionValue = 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 db578e32663f..03bb8d03d1db 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -319,7 +319,7 @@ class Completions( val ScalaCliCompletions = new ScalaCliCompletions(coursierComplete, pos, text) - path match + val (advanced, exclusive) = path match case ScalaCliCompletions(dependency) => (ScalaCliCompletions.contribute(dependency), true) @@ -525,7 +525,10 @@ class Completions( config.isCompletionSnippetsEnabled() ) (args, false) - end match + val singletonCompletions = InterCompletionType.inferType(path).map( + SingletonCompletions.contribute(path, _, completionPos) + ).getOrElse(Nil) + (singletonCompletions ++ advanced, exclusive) end advancedCompletions private def isAmmoniteCompletionPosition( @@ -704,6 +707,7 @@ class Completions( case fileSysMember: CompletionValue.FileSystemMember => (fileSysMember.label, true) case ii: CompletionValue.IvyImport => (ii.label, true) + case sv: CompletionValue.SingletonValue => (sv.label, true) if !alreadySeen(id) && include then alreadySeen += id @@ -911,38 +915,18 @@ class Completions( else 2 } ) - - /** - * This one is used for the following case: - * ```scala - * def foo(argument: Int): Int = ??? - * val argument = 42 - * foo(arg@@) // completions should be ordered as : - * // - argument (local val) - actual value comes first - * // - argument = ... (named arg) - named arg after - * // - ... all other options - * ``` - */ - def compareInApplyParams(o1: CompletionValue, o2: CompletionValue): Int = - def priority(v: CompletionValue): Int = - v match - case _: CompletionValue.Compiler => 0 - case CompletionValue.ExtraMethod(_, _: CompletionValue.Compiler) => 0 - case _ => 1 - - priority(o1) - priority(o2) - end compareInApplyParams - - def prioritizeKeywords(o1: CompletionValue, o2: CompletionValue): Int = + def prioritizeByClass(o1: CompletionValue, o2: CompletionValue): Int = def priority(v: CompletionValue): Int = v match - case _: CompletionValue.CaseKeyword => 0 - case _: CompletionValue.NamedArg => 1 - case _: CompletionValue.Keyword => 2 - case _ => 3 + case _: CompletionValue.SingletonValue => 0 + case _: CompletionValue.Compiler => 1 + case _: CompletionValue.CaseKeyword => 2 + case _: CompletionValue.NamedArg => 3 + case _: CompletionValue.Keyword => 4 + case _ => 5 priority(o1) - priority(o2) - end prioritizeKeywords + end prioritizeByClass /** * Some completion values should be shown first such as CaseKeyword and * NamedArg @@ -1041,12 +1025,9 @@ class Completions( end if end if case _ => - val byApplyParams = compareInApplyParams(o1, o2) - if byApplyParams != 0 then byApplyParams - else - val keywords = prioritizeKeywords(o1, o2) - if keywords != 0 then keywords - else compareByRelevance(o1, o2) + val byClass = prioritizeByClass(o1, o2) + if byClass != 0 then byClass + else compareByRelevance(o1, o2) end compare end Completions diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala new file mode 100644 index 000000000000..769f47114c98 --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala @@ -0,0 +1,133 @@ +package dotty.tools.pc.completions + +import scala.meta.internal.metals.Fuzzy +import dotty.tools.pc.utils.InteractiveEnrichments.* +import dotty.tools.pc.completions.CompletionValue.SingletonValue + +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.Flags +import dotty.tools.dotc.core.StdNames +import dotty.tools.dotc.core.Symbols +import dotty.tools.dotc.core.Types.AndType +import dotty.tools.dotc.core.Types.AppliedType +import dotty.tools.dotc.core.Types.ConstantType +import dotty.tools.dotc.core.Types.OrType +import dotty.tools.dotc.core.Types.TermRef +import dotty.tools.dotc.core.Types.Type +import dotty.tools.dotc.core.Types.TypeRef +import dotty.tools.dotc.util.Spans.Span +import dotty.tools.dotc.core.Symbols.defn + +object SingletonCompletions: + def contribute( + path: List[Tree], + tpe0: Type, + completionPos: CompletionPos + )(using ctx: Context): List[CompletionValue] = + for { + (name, span) <- + path match + case (i @ Ident(name)) :: _ => List(name.toString() -> i.span) + case (l @ Literal(const)) :: _ => List(const.show -> l.span) + case _ => Nil + query = name.replace(Cursor.value, "") + tpe = tpe0 match + // for Tuple 2 we want to suggest first arg completion + case AppliedType(t: TypeRef, args) if t.classSymbol == Symbols.defn.Tuple2 && args.nonEmpty => + args.head + case t => t + singletonValues = collectSingletons(tpe).map(_.show) + range = completionPos.originalCursorPosition.withStart(span.start).withEnd(span.start + query.length).toLsp + value <- singletonValues.collect { + case name if Fuzzy.matches(query, name) => + SingletonValue(name, tpe, Some(range)) + } + } yield value + + private def collectSingletons(tpe: Type)(using Context): List[Constant] = + tpe.deepDealias match + case ConstantType(value) => List(value) + case OrType(tpe1, tpe2) => + collectSingletons(tpe1) ++ collectSingletons(tpe2) + case AndType(tpe1, tpe2) => + collectSingletons(tpe1).intersect(collectSingletons(tpe2)) + case _ => Nil + +object InterCompletionType: + def inferType(path: List[Tree])(using Context): Option[Type] = + path match + case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(s: Select)) :: rest if s.symbol == defn.Predef_undefined => + inferType(rest, lit.span) + case ident :: rest => inferType(rest, ident.span) + case _ => None + + def inferType(path: List[Tree], span: Span)(using Context): Option[Type] = + path match + case Apply(head, List(p : Select)) :: rest if p.name == StdNames.nme.??? && p.qualifier.symbol.name == StdNames.nme.Predef && p.span.isSynthetic => + inferType(rest, span) + case Block(_, expr) :: rest if expr.span.contains(span) => + inferType(rest, span) + case If(cond, _, _) :: rest if !cond.span.contains(span) => + inferType(rest, span) + case Typed(expr, tpt) :: _ if expr.span.contains(span) && !tpt.tpe.isErroneous => Some(tpt.tpe) + case Block(_, expr) :: rest if expr.span.contains(span) => + inferType(rest, span) + case Bind(_, body) :: rest if body.span.contains(span) => inferType(rest, span) + case Alternative(_) :: rest => inferType(rest, span) + case Try(block, _, _) :: rest if block.span.contains(span) => inferType(rest, span) + case CaseDef(_, _, body) :: Try(_, cases, _) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => inferType(rest, span) + case If(cond, _, _) :: rest if !cond.span.contains(span) => inferType(rest, span) + case CaseDef(_, _, body) :: Match(_, cases) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => + inferType(rest, span) + case NamedArg(_, arg) :: rest if arg.span.contains(span) => inferType(rest, span) + // x match + // case @@ + case CaseDef(pat, _, _) :: Match(sel, cases) :: rest if pat.span.contains(span) && cases.exists(_.span.contains(span)) && !sel.tpe.isErroneous => + sel.tpe match + case tpe: TermRef => Some(tpe.symbol.info).filterNot(_.isErroneous) + case tpe => Some(tpe) + // List(@@) + case SeqLiteral(_, tpe) :: _ if !tpe.tpe.isErroneous => + Some(tpe.tpe) + // val _: T = @@ + // def _: T = @@ + case (defn: ValOrDefDef) :: rest if !defn.tpt.tpe.isErroneous => Some(defn.tpt.tpe) + // f(@@) + case (app: Apply) :: rest => + val param = + for { + ind <- app.args.zipWithIndex.collectFirst { + case (arg, id) if arg.span.contains(span) => id + } + params <- app.symbol.paramSymss.find(!_.exists(_.isTypeParam)) + param <- params.get(ind) + } yield param.info + param match + // def f[T](a: T): T = ??? + // f[Int](@@) + // val _: Int = f(@@) + case Some(t : TypeRef) if t.symbol.is(Flags.TypeParam) => + for { + (typeParams, args) <- + app match + case Apply(TypeApply(fun, args), _) => + val typeParams = fun.symbol.paramSymss.headOption.filter(_.forall(_.isTypeParam)) + typeParams.map((_, args.map(_.tpe))) + // val f: (j: "a") => Int + // f(@@) + case Apply(Select(v, StdNames.nme.apply), _) => + v.symbol.info match + case AppliedType(des, args) => + Some((des.typeSymbol.typeParams, args)) + case _ => None + case _ => None + ind = typeParams.indexOf(t.symbol) + tpe <- args.get(ind) + if !tpe.isErroneous + } yield tpe + case Some(tpe) => Some(tpe) + case _ => None + case _ => None + diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala new file mode 100644 index 000000000000..25d1418900fd --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/SingletonCompletionsSuite.scala @@ -0,0 +1,300 @@ +package dotty.tools.pc.tests.completion + +import dotty.tools.pc.base.BaseCompletionSuite + +import org.junit.Test + +class SingletonCompletionsSuite extends BaseCompletionSuite { + + @Test def `basic` = + check( + """|val k: 1 = @@ + |""".stripMargin, + "1: 1", + topLines = Some(1) + ) + + @Test def `literal` = + check( + """|val k: 1 = 1@@ + |""".stripMargin, + "1: 1", + topLines = Some(1) + ) + + @Test def `string` = + check( + """|val k: "aaa" = "@@" + |""".stripMargin, + """|"aaa": "aaa" + |""".stripMargin + ) + + @Test def `string-edit` = + checkEdit( + """|val k: "aaa" = "@@" + |""".stripMargin, + """|val k: "aaa" = "aaa" + |""".stripMargin, + assertSingleItem = false + ) + + @Test def `string-edit-2` = + checkEdit( + """|val k: "aaa" = @@ //something + |""".stripMargin, + """|val k: "aaa" = "aaa" //something + |""".stripMargin, + assertSingleItem = false + ) + + @Test def `union` = + check( + """|val k: "aaa" | "bbb" = "@@" + |""".stripMargin, + """|"aaa": "aaa" | "bbb" + |"bbb": "aaa" | "bbb" + |""".stripMargin + ) + + @Test def `type-alias-union` = + check( + """|type Color = "red" | "green" | "blue" + |val c: Color = "r@@" + |""".stripMargin, + """|"red": Color + |""".stripMargin + ) + + @Test def `param` = + check( + """|type Color = "red" | "green" | "blue" + |def paint(c: Color) = ??? + |val _ = paint(@@) + |""".stripMargin, + """|"red": Color + |"green": Color + |"blue": Color + |c = : Color + |""".stripMargin, + topLines = Some(4) + ) + + @Test def `with-block` = + check( + """|type Color = "red" | "green" | "blue" + |def c: Color = { + | "r@@" + |} + |""".stripMargin, + """|"red": Color + |""".stripMargin + ) + + @Test def `if-statement` = + check( + """|type Color = "red" | "green" | "blue" + |def c(shouldBeBlue: Boolean): Color = { + | if(shouldBeBlue) "b@@" + | else "red" + |} + |""".stripMargin, + """|"blue": Color + |""".stripMargin + ) + + @Test def `if-statement-2` = + check( + """|type Color = "red" | "green" | "blue" + |def c(shouldBeBlue: Boolean): Color = { + | if(shouldBeBlue) { + | println("is blue") + | "b@@" + | } else "red" + |} + |""".stripMargin, + """|"blue": Color + |""".stripMargin + ) + + @Test def `if-statement-3` = + check( + """|type Color = "red" | "green" | "blue" + |def c(shouldBeBlue: Boolean): Color = { + | if(shouldBeBlue) { + | "b@@" + | println("is blue") + | "blue" + | } else "red" + |} + |""".stripMargin, + """""".stripMargin + ) + + @Test def `middle-of-a-block` = + check( + """|type Color = "red" | "green" | "blue" + |def c: Color = { + | "r@@" + | ??? + |} + |""".stripMargin, + "" + ) + + @Test def overloaded = + check( + """| + |type Color = "red" | "green" | "blue" + |def foo(i: Int) = ??? + |def foo(c: Color) = ??? + | + |def c = foo(@@) + |""".stripMargin, + """|c = : Color + |i = : Int + |""".stripMargin, + topLines = Some(2) + ) + + @Test def `and-type` = + check( + """|type Color = "red" | "green" | "blue" | "black" + |type FordColor = Color & "black" + |val i: FordColor = "@@" + |""".stripMargin, + """|"black": FordColor + |""".stripMargin + ) + + @Test def list = + check( + """|type Color = "red" | "green" | "blue" + |val i: List[Color] = List("@@") + |""".stripMargin, + """|"red": "red" | "green" | "blue" + |"green": "red" | "green" | "blue" + |"blue": "red" | "green" | "blue" + |""".stripMargin + ) + + @Test def option = + check( + """|type Color = "red" | "green" | "blue" + |val i: Option[Color] = Some("@@") + |""".stripMargin, + """|"red": "red" | "green" | "blue" + |"green": "red" | "green" | "blue" + |"blue": "red" | "green" | "blue" + |""".stripMargin + ) + + @Test def map = + check( + """|type Color = "red" | "green" | "blue" + |val i: Option[Int] = Some(1) + |val g: Option[Color] = i.map { _ => "@@" } + |""".stripMargin, + """|"red": "red" | "green" | "blue" + |"green": "red" | "green" | "blue" + |"blue": "red" | "green" | "blue" + |""".stripMargin + ) + + @Test def `some-for-comp` = + check( + """|type Color = "red" | "green" | "blue" + |val i: Option[Int] = Some(1) + |val g: Option[Color] = + | for + | _ <- i + | yield "@@" + |""".stripMargin, + """|"red": "red" | "green" | "blue" + |"green": "red" | "green" | "blue" + |"blue": "red" | "green" | "blue" + |""".stripMargin + ) + + @Test def `some-for-comp-1` = + check( + """|type Color = "red" | "green" | "blue" + |val i: Option[Int] = Some(1) + |val g: Option[Color] = + | for + | _ <- i + | _ <- i + | if i > 2 + | yield "@@" + |""".stripMargin, + """|"red": "red" | "green" | "blue" + |"green": "red" | "green" | "blue" + |"blue": "red" | "green" | "blue" + |""".stripMargin + ) + + @Test def lambda = + check( + """|def m = + | val j = (f: "foo") => 1 + | j("f@@") + |""".stripMargin, + """|"foo": "foo" + |""".stripMargin + ) + + @Test def `match-case-result` = + check( + """|val h: "foo" = + | 1 match + | case _ => "@@" + |""".stripMargin, + """|"foo": "foo" + |""".stripMargin + ) + + @Test def `dont-show-on-select` = + check( + """|val f: "foo" = List(1,2,3).@@ + |""".stripMargin, + "", + filter = _ == "\"foo\": \"foo\"" + ) + + @Test def `match-case` = + check( + """|def h(foo: "foo") = + | foo match + | case "@@" => + |""".stripMargin, + """|"foo": "foo" + |""".stripMargin + ) + + @Test def `match-case2` = + check( + """|def h = + | ("foo" : "foo") match + | case "@@" => + |""".stripMargin, + """|"foo": "foo" + |""".stripMargin + ) + + @Test def `named-args` = + check( + """|def h(foo: "foo") = ??? + |def k = h(foo = "@@") + |""".stripMargin, + """|"foo": "foo" + |""".stripMargin + ) + + @Test def `map-type` = + check( + """|def m = Map["foo", Int]("@@") + |""".stripMargin, + """|"foo": "foo" + |""".stripMargin + ) +} From 637b1d3dc3ddddd0cbe3e2a4ac5ec3e972a6ad05 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 16 Jul 2024 12:58:05 +0200 Subject: [PATCH 5/6] deal with unsafe nulls and fix sorting --- .../main/dotty/tools/pc/completions/Completions.scala | 1 + .../tools/pc/completions/SingletonCompletions.scala | 2 +- .../test/dotty/tools/pc/tests/PcReferencesSuite.scala | 10 +++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) 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 03bb8d03d1db..a517df60e833 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -920,6 +920,7 @@ class Completions( v match case _: CompletionValue.SingletonValue => 0 case _: CompletionValue.Compiler => 1 + case CompletionValue.ExtraMethod(_, _: CompletionValue.Compiler) => 1 case _: CompletionValue.CaseKeyword => 2 case _: CompletionValue.NamedArg => 3 case _: CompletionValue.Keyword => 4 diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala index 769f47114c98..6e59c9afca3a 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/SingletonCompletions.scala @@ -32,7 +32,7 @@ object SingletonCompletions: case (i @ Ident(name)) :: _ => List(name.toString() -> i.span) case (l @ Literal(const)) :: _ => List(const.show -> l.span) case _ => Nil - query = name.replace(Cursor.value, "") + query = name.replace(Cursor.value, "").nn tpe = tpe0 match // for Tuple 2 we want to suggest first arg completion case AppliedType(t: TypeRef, args) if t.classSymbol == Symbols.defn.Tuple2 && args.nonEmpty => diff --git a/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala index aee3fd37617f..15ee35928872 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/PcReferencesSuite.scala @@ -1,12 +1,16 @@ package dotty.tools.pc.tests + +import scala.language.unsafeNulls + import dotty.tools.pc.base.BasePCSuite import dotty.tools.pc.utils.RangeReplace -import scala.meta.internal.pc.PcReferencesRequest -import scala.meta.internal.metals.CompilerVirtualFileParams + import java.net.URI -import scala.meta.internal.metals.EmptyCancelToken import org.eclipse.lsp4j.jsonrpc.messages.{Either => JEither} import scala.meta.internal.jdk.CollectionConverters.* +import scala.meta.internal.metals.CompilerVirtualFileParams +import scala.meta.internal.metals.EmptyCancelToken +import scala.meta.internal.pc.PcReferencesRequest import org.junit.Test From 875af444d008f84dedc39cc531779c0dc76a895c Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 25 Jul 2024 12:39:48 +0200 Subject: [PATCH 6/6] chore: bump mtags version --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 8c9a0e69f0ef..4f90566a60f9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1359,7 +1359,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.3.3" + val mtagsVersion = "1.3.4" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0",