Skip to content

Commit c597521

Browse files
Backport "Backport from metals" to 3.5.2 (#21462)
Backports #21196 to the 3.5.2 branch. PR submitted by the release tooling. [skip ci]
2 parents 1530c63 + 86db721 commit c597521

12 files changed

+615
-48
lines changed

Diff for: presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala

+8-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,14 @@ object AutoImports:
269269
private def importName(sym: Symbol): String =
270270
if indexedContext.importContext.toplevelClashes(sym) then
271271
s"_root_.${sym.fullNameBackticked(false)}"
272-
else sym.fullNameBackticked(false)
272+
else
273+
sym.ownersIterator.zipWithIndex.foldLeft((List.empty[String], false)) { case ((acc, isDone), (sym, idx)) =>
274+
if(isDone || sym.isEmptyPackage || sym.isRoot) (acc, true)
275+
else indexedContext.rename(sym) match
276+
case Some(renamed) => (renamed :: acc, true)
277+
case None if !sym.isPackageObject => (sym.nameBackticked(false) :: acc, false)
278+
case None => (acc, false)
279+
}._1.mkString(".")
273280
end AutoImportsGenerator
274281

275282
private def autoImportPosition(

Diff for: presentation-compiler/src/main/dotty/tools/pc/PcCollector.scala

+9-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ trait PcCollector[T]:
3535
parent: Option[Tree]
3636
)(tree: Tree| EndMarker, pos: SourcePosition, symbol: Option[Symbol]): T
3737

38+
def allowZeroExtentImplicits: Boolean = false
39+
3840
def resultAllOccurences(): Set[T] =
3941
def noTreeFilter = (_: Tree) => true
4042
def noSoughtFilter = (_: Symbol => Boolean) => true
@@ -87,6 +89,10 @@ trait PcCollector[T]:
8789
def isCorrect =
8890
!span.isZeroExtent && span.exists && span.start < sourceText.size && span.end <= sourceText.size
8991

92+
extension (tree: Tree)
93+
def isCorrectSpan =
94+
tree.span.isCorrect || (allowZeroExtentImplicits && tree.symbol.is(Flags.Implicit))
95+
9096
def traverseSought(
9197
filter: Tree => Boolean,
9298
soughtFilter: (Symbol => Boolean) => Boolean
@@ -107,7 +113,7 @@ trait PcCollector[T]:
107113
* All indentifiers such as:
108114
* val a = <<b>>
109115
*/
110-
case ident: Ident if ident.span.isCorrect && filter(ident) =>
116+
case ident: Ident if ident.isCorrectSpan && filter(ident) =>
111117
// symbols will differ for params in different ext methods, but source pos will be the same
112118
if soughtFilter(_.sourcePos == ident.symbol.sourcePos)
113119
then
@@ -122,7 +128,7 @@ trait PcCollector[T]:
122128
* val x = new <<A>>(1)
123129
*/
124130
case sel @ Select(New(t), _)
125-
if sel.span.isCorrect &&
131+
if sel.isCorrectSpan &&
126132
sel.symbol.isConstructor &&
127133
t.symbol == NoSymbol =>
128134
if soughtFilter(_ == sel.symbol.owner) then
@@ -137,7 +143,7 @@ trait PcCollector[T]:
137143
* val a = hello.<<b>>
138144
*/
139145
case sel: Select
140-
if sel.span.isCorrect && filter(sel) &&
146+
if sel.isCorrectSpan && filter(sel) &&
141147
!sel.isForComprehensionMethod =>
142148
occurrences + collect(
143149
sel,

Diff for: presentation-compiler/src/main/dotty/tools/pc/PcReferencesProvider.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class PcReferencesProvider(
2323
request: ReferencesRequest,
2424
) extends WithCompilationUnit(driver, request.file()) with PcCollector[Option[(String, Option[lsp4j.Range])]]:
2525

26+
override def allowZeroExtentImplicits: Boolean = true
27+
2628
private def soughtSymbols =
2729
if(request.offsetOrSymbol().isLeft()) {
2830
val offsetParams = CompilerOffsetParams(
@@ -64,4 +66,4 @@ class PcReferencesProvider(
6466
}
6567
.toList
6668
case _ => Nil
67-
end PcReferencesProvider
69+
end PcReferencesProvider

Diff for: presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dotty.tools.pc
22

3+
import scala.collection.mutable
34
import scala.util.control.NonFatal
45

56
import scala.meta.pc.PcSymbolKind
@@ -37,11 +38,25 @@ class SymbolInformationProvider(using Context):
3738
if classSym.isClass
3839
then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName)
3940
else Nil
41+
val allParents =
42+
val visited = mutable.Set[Symbol]()
43+
def collect(sym: Symbol): Unit = {
44+
visited += sym
45+
if sym.isClass
46+
then sym.asClass.parentSyms.foreach {
47+
case parent if !visited(parent) =>
48+
collect(parent)
49+
case _ =>
50+
}
51+
}
52+
collect(classSym)
53+
visited.toList.map(SemanticdbSymbols.symbolName)
4054
val dealisedSymbol =
4155
if sym.isAliasType then sym.info.deepDealias.typeSymbol else sym
4256
val classOwner =
4357
sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module))
4458
val overridden = sym.denot.allOverriddenSymbols.toList
59+
val memberDefAnnots = sym.info.membersBasedOnFlags(Flags.Method, Flags.EmptyFlags).flatMap(_.allSymbols).flatMap(_.denot.annotations)
4560

4661
val pcSymbolInformation =
4762
PcSymbolInformation(
@@ -56,6 +71,9 @@ class SymbolInformationProvider(using Context):
5671
properties =
5772
if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT)
5873
else Nil,
74+
recursiveParents = allParents,
75+
annotations = sym.denot.annotations.map(_.symbol.showFullName),
76+
memberDefsAnnotations = memberDefAnnots.map(_.symbol.showFullName).toList
5977
)
6078

6179
Some(pcSymbolInformation)

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/CompletionValue.scala

+9
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,15 @@ object CompletionValue:
354354
description
355355
override def insertMode: Option[InsertTextMode] = Some(InsertTextMode.AsIs)
356356

357+
case class SingletonValue(label: String, info: Type, override val range: Option[Range])
358+
extends CompletionValue:
359+
override def insertText: Option[String] = Some(label)
360+
override def labelWithDescription(printer: ShortenedTypePrinter)(using Context): String =
361+
s"$label: ${printer.tpe(info)}"
362+
363+
override def completionItemKind(using Context): CompletionItemKind =
364+
CompletionItemKind.Constant
365+
357366
def namedArg(label: String, sym: ParamSymbol)(using
358367
Context
359368
): CompletionValue =

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala

+18-36
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ class Completions(
323323
val ScalaCliCompletions =
324324
new ScalaCliCompletions(coursierComplete, pos, text)
325325

326-
path match
326+
val (advanced, exclusive) = path match
327327
case ScalaCliCompletions(dependency) =>
328328
(ScalaCliCompletions.contribute(dependency), true)
329329

@@ -529,7 +529,10 @@ class Completions(
529529
config.isCompletionSnippetsEnabled()
530530
)
531531
(args, false)
532-
end match
532+
val singletonCompletions = InterCompletionType.inferType(path).map(
533+
SingletonCompletions.contribute(path, _, completionPos)
534+
).getOrElse(Nil)
535+
(singletonCompletions ++ advanced, exclusive)
533536
end advancedCompletions
534537

535538
private def isAmmoniteCompletionPosition(
@@ -708,6 +711,7 @@ class Completions(
708711
case fileSysMember: CompletionValue.FileSystemMember =>
709712
(fileSysMember.label, true)
710713
case ii: CompletionValue.IvyImport => (ii.label, true)
714+
case sv: CompletionValue.SingletonValue => (sv.label, true)
711715

712716
if !alreadySeen(id) && include then
713717
alreadySeen += id
@@ -915,38 +919,19 @@ class Completions(
915919
else 2
916920
}
917921
)
918-
919-
/**
920-
* This one is used for the following case:
921-
* ```scala
922-
* def foo(argument: Int): Int = ???
923-
* val argument = 42
924-
* foo(arg@@) // completions should be ordered as :
925-
* // - argument (local val) - actual value comes first
926-
* // - argument = ... (named arg) - named arg after
927-
* // - ... all other options
928-
* ```
929-
*/
930-
def compareInApplyParams(o1: CompletionValue, o2: CompletionValue): Int =
931-
def priority(v: CompletionValue): Int =
932-
v match
933-
case _: CompletionValue.Compiler => 0
934-
case CompletionValue.ExtraMethod(_, _: CompletionValue.Compiler) => 0
935-
case _ => 1
936-
937-
priority(o1) - priority(o2)
938-
end compareInApplyParams
939-
940-
def prioritizeKeywords(o1: CompletionValue, o2: CompletionValue): Int =
922+
def prioritizeByClass(o1: CompletionValue, o2: CompletionValue): Int =
941923
def priority(v: CompletionValue): Int =
942924
v match
943-
case _: CompletionValue.CaseKeyword => 0
944-
case _: CompletionValue.NamedArg => 1
945-
case _: CompletionValue.Keyword => 2
946-
case _ => 3
925+
case _: CompletionValue.SingletonValue => 0
926+
case _: CompletionValue.Compiler => 1
927+
case CompletionValue.ExtraMethod(_, _: CompletionValue.Compiler) => 1
928+
case _: CompletionValue.CaseKeyword => 2
929+
case _: CompletionValue.NamedArg => 3
930+
case _: CompletionValue.Keyword => 4
931+
case _ => 5
947932

948933
priority(o1) - priority(o2)
949-
end prioritizeKeywords
934+
end prioritizeByClass
950935
/**
951936
* Some completion values should be shown first such as CaseKeyword and
952937
* NamedArg
@@ -1045,12 +1030,9 @@ class Completions(
10451030
end if
10461031
end if
10471032
case _ =>
1048-
val byApplyParams = compareInApplyParams(o1, o2)
1049-
if byApplyParams != 0 then byApplyParams
1050-
else
1051-
val keywords = prioritizeKeywords(o1, o2)
1052-
if keywords != 0 then keywords
1053-
else compareByRelevance(o1, o2)
1033+
val byClass = prioritizeByClass(o1, o2)
1034+
if byClass != 0 then byClass
1035+
else compareByRelevance(o1, o2)
10541036
end compare
10551037

10561038
end Completions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package dotty.tools.pc.completions
2+
3+
import scala.meta.internal.metals.Fuzzy
4+
import dotty.tools.pc.utils.InteractiveEnrichments.*
5+
import dotty.tools.pc.completions.CompletionValue.SingletonValue
6+
7+
import dotty.tools.dotc.ast.tpd.*
8+
import dotty.tools.dotc.core.Constants.Constant
9+
import dotty.tools.dotc.core.Contexts.Context
10+
import dotty.tools.dotc.core.Flags
11+
import dotty.tools.dotc.core.StdNames
12+
import dotty.tools.dotc.core.Symbols
13+
import dotty.tools.dotc.core.Types.AndType
14+
import dotty.tools.dotc.core.Types.AppliedType
15+
import dotty.tools.dotc.core.Types.ConstantType
16+
import dotty.tools.dotc.core.Types.OrType
17+
import dotty.tools.dotc.core.Types.TermRef
18+
import dotty.tools.dotc.core.Types.Type
19+
import dotty.tools.dotc.core.Types.TypeRef
20+
import dotty.tools.dotc.util.Spans.Span
21+
import dotty.tools.dotc.core.Symbols.defn
22+
23+
object SingletonCompletions:
24+
def contribute(
25+
path: List[Tree],
26+
tpe0: Type,
27+
completionPos: CompletionPos
28+
)(using ctx: Context): List[CompletionValue] =
29+
for {
30+
(name, span) <-
31+
path match
32+
case (i @ Ident(name)) :: _ => List(name.toString() -> i.span)
33+
case (l @ Literal(const)) :: _ => List(const.show -> l.span)
34+
case _ => Nil
35+
query = name.replace(Cursor.value, "").nn
36+
tpe = tpe0 match
37+
// for Tuple 2 we want to suggest first arg completion
38+
case AppliedType(t: TypeRef, args) if t.classSymbol == Symbols.defn.Tuple2 && args.nonEmpty =>
39+
args.head
40+
case t => t
41+
singletonValues = collectSingletons(tpe).map(_.show)
42+
range = completionPos.originalCursorPosition.withStart(span.start).withEnd(span.start + query.length).toLsp
43+
value <- singletonValues.collect {
44+
case name if Fuzzy.matches(query, name) =>
45+
SingletonValue(name, tpe, Some(range))
46+
}
47+
} yield value
48+
49+
private def collectSingletons(tpe: Type)(using Context): List[Constant] =
50+
tpe.deepDealias match
51+
case ConstantType(value) => List(value)
52+
case OrType(tpe1, tpe2) =>
53+
collectSingletons(tpe1) ++ collectSingletons(tpe2)
54+
case AndType(tpe1, tpe2) =>
55+
collectSingletons(tpe1).intersect(collectSingletons(tpe2))
56+
case _ => Nil
57+
58+
object InterCompletionType:
59+
def inferType(path: List[Tree])(using Context): Option[Type] =
60+
path match
61+
case (lit: Literal) :: Select(Literal(_), _) :: Apply(Select(Literal(_), _), List(s: Select)) :: rest if s.symbol == defn.Predef_undefined =>
62+
inferType(rest, lit.span)
63+
case ident :: rest => inferType(rest, ident.span)
64+
case _ => None
65+
66+
def inferType(path: List[Tree], span: Span)(using Context): Option[Type] =
67+
path match
68+
case Apply(head, List(p : Select)) :: rest if p.name == StdNames.nme.??? && p.qualifier.symbol.name == StdNames.nme.Predef && p.span.isSynthetic =>
69+
inferType(rest, span)
70+
case Block(_, expr) :: rest if expr.span.contains(span) =>
71+
inferType(rest, span)
72+
case If(cond, _, _) :: rest if !cond.span.contains(span) =>
73+
inferType(rest, span)
74+
case Typed(expr, tpt) :: _ if expr.span.contains(span) && !tpt.tpe.isErroneous => Some(tpt.tpe)
75+
case Block(_, expr) :: rest if expr.span.contains(span) =>
76+
inferType(rest, span)
77+
case Bind(_, body) :: rest if body.span.contains(span) => inferType(rest, span)
78+
case Alternative(_) :: rest => inferType(rest, span)
79+
case Try(block, _, _) :: rest if block.span.contains(span) => inferType(rest, span)
80+
case CaseDef(_, _, body) :: Try(_, cases, _) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) => inferType(rest, span)
81+
case If(cond, _, _) :: rest if !cond.span.contains(span) => inferType(rest, span)
82+
case CaseDef(_, _, body) :: Match(_, cases) :: rest if body.span.contains(span) && cases.exists(_.span.contains(span)) =>
83+
inferType(rest, span)
84+
case NamedArg(_, arg) :: rest if arg.span.contains(span) => inferType(rest, span)
85+
// x match
86+
// case @@
87+
case CaseDef(pat, _, _) :: Match(sel, cases) :: rest if pat.span.contains(span) && cases.exists(_.span.contains(span)) && !sel.tpe.isErroneous =>
88+
sel.tpe match
89+
case tpe: TermRef => Some(tpe.symbol.info).filterNot(_.isErroneous)
90+
case tpe => Some(tpe)
91+
// List(@@)
92+
case SeqLiteral(_, tpe) :: _ if !tpe.tpe.isErroneous =>
93+
Some(tpe.tpe)
94+
// val _: T = @@
95+
// def _: T = @@
96+
case (defn: ValOrDefDef) :: rest if !defn.tpt.tpe.isErroneous => Some(defn.tpt.tpe)
97+
// f(@@)
98+
case (app: Apply) :: rest =>
99+
val param =
100+
for {
101+
ind <- app.args.zipWithIndex.collectFirst {
102+
case (arg, id) if arg.span.contains(span) => id
103+
}
104+
params <- app.symbol.paramSymss.find(!_.exists(_.isTypeParam))
105+
param <- params.get(ind)
106+
} yield param.info
107+
param match
108+
// def f[T](a: T): T = ???
109+
// f[Int](@@)
110+
// val _: Int = f(@@)
111+
case Some(t : TypeRef) if t.symbol.is(Flags.TypeParam) =>
112+
for {
113+
(typeParams, args) <-
114+
app match
115+
case Apply(TypeApply(fun, args), _) =>
116+
val typeParams = fun.symbol.paramSymss.headOption.filter(_.forall(_.isTypeParam))
117+
typeParams.map((_, args.map(_.tpe)))
118+
// val f: (j: "a") => Int
119+
// f(@@)
120+
case Apply(Select(v, StdNames.nme.apply), _) =>
121+
v.symbol.info match
122+
case AppliedType(des, args) =>
123+
Some((des.typeSymbol.typeParams, args))
124+
case _ => None
125+
case _ => None
126+
ind = typeParams.indexOf(t.symbol)
127+
tpe <- args.get(ind)
128+
if !tpe.isErroneous
129+
} yield tpe
130+
case Some(tpe) => Some(tpe)
131+
case _ => None
132+
case _ => None
133+

0 commit comments

Comments
 (0)