Skip to content

Commit

Permalink
Backport from metals (#21196)
Browse files Browse the repository at this point in the history
  • Loading branch information
tgodzik authored Jul 25, 2024
2 parents ad22fa6 + 875af44 commit dec395b
Show file tree
Hide file tree
Showing 12 changed files with 615 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 9 additions & 3 deletions presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -107,7 +113,7 @@ trait PcCollector[T]:
* All indentifiers such as:
* val a = <<b>>
*/
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
Expand All @@ -122,7 +128,7 @@ trait PcCollector[T]:
* val x = new <<A>>(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
Expand All @@ -137,7 +143,7 @@ trait PcCollector[T]:
* val a = hello.<<b>>
*/
case sel: Select
if sel.span.isCorrect && filter(sel) &&
if sel.isCorrectSpan && filter(sel) &&
!sel.isForComprehensionMethod =>
occurrences + collect(
sel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -64,4 +66,4 @@ class PcReferencesProvider(
}
.toList
case _ => Nil
end PcReferencesProvider
end PcReferencesProvider
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dotty.tools.pc

import scala.collection.mutable
import scala.util.control.NonFatal

import scala.meta.pc.PcSymbolKind
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class Completions(
val ScalaCliCompletions =
new ScalaCliCompletions(coursierComplete, pos, text)

path match
val (advanced, exclusive) = path match
case ScalaCliCompletions(dependency) =>
(ScalaCliCompletions.contribute(dependency), true)

Expand Down Expand Up @@ -529,7 +529,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(
Expand Down Expand Up @@ -708,6 +711,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
Expand Down Expand Up @@ -915,38 +919,19 @@ 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.ExtraMethod(_, _: 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
Expand Down Expand Up @@ -1045,12 +1030,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
Original file line number Diff line number Diff line change
@@ -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, "").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 =>
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

Loading

0 comments on commit dec395b

Please sign in to comment.