Skip to content

Commit 3140bde

Browse files
committed
Code completions - reworks - part 2
1 parent ba382b9 commit 3140bde

File tree

4 files changed

+146
-108
lines changed

4 files changed

+146
-108
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 90 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -106,31 +106,25 @@ object Completion {
106106
case _ => 0
107107
}
108108

109-
/** Create a new `CompletionBuffer` for completing at `pos`. */
110-
private def completionBuffer(path: List[Tree], pos: SourcePosition): CompletionBuffer = {
109+
private def computeCompletions(pos: SourcePosition, path: List[Tree])(using Context): (Int, List[Completion]) = {
111110
val mode = completionMode(path, pos)
112111
val prefix = completionPrefix(path, pos)
113-
new CompletionBuffer(mode, prefix, pos)
114-
}
115-
116-
private def computeCompletions(pos: SourcePosition, path: List[Tree])(using Context): (Int, List[Completion]) = {
117-
118-
val offset = completionOffset(path)
119-
val buffer = completionBuffer(path, pos)
112+
val completer = new Completer(mode, prefix, pos)
120113

121114
val scope = path match {
122-
case Select(qual, _) :: _ => buffer.selectionCompletions(path, qual)
123-
case Import(expr, _) :: _ => buffer.directMemberCompletions(expr)
124-
case (_: untpd.ImportSelector) :: Import(expr, _) :: _ => buffer.directMemberCompletions(expr)
125-
case _ => buffer.scopeCompletions
115+
case Select(qual, _) :: _ => completer.selectionCompletions(path, qual)
116+
case Import(expr, _) :: _ => completer.directMemberCompletions(expr)
117+
case (_: untpd.ImportSelector) :: Import(expr, _) :: _ => completer.directMemberCompletions(expr)
118+
case _ => completer.scopeCompletions
126119
}
127120

128121
val completionList = scope.getCompletions
122+
val offset = completionOffset(path)
129123

130124
interactiv.println(i"""completion with pos = $pos,
131-
| prefix = ${buffer.prefix},
132-
| term = ${buffer.mode.is(Mode.Term)},
133-
| type = ${buffer.mode.is(Mode.Type)}
125+
| prefix = ${completer.prefix},
126+
| term = ${completer.mode.is(Mode.Term)},
127+
| type = ${completer.mode.is(Mode.Type)}
134128
| results = $completionList%, %""")
135129
(offset, completionList)
136130
}
@@ -156,7 +150,12 @@ object Completion {
156150
""
157151
}
158152

159-
private class CompletionBuffer(val mode: Mode, val prefix: String, pos: SourcePosition) {
153+
/** Computes code completions depending on the context in which completion is requested
154+
* @param mode Should complete names of terms, types or both
155+
* @param prefix The prefix that all suggested completions should start with
156+
* @param pos Cursor position where completion was requested
157+
*/
158+
private class Completer(val mode: Mode, val prefix: String, pos: SourcePosition) {
160159
/** Completions for terms and types that are currently in scope:
161160
* the members of the current class and the symbols that have been imported, recursively adding completions from outer scopes
162161
*/
@@ -167,13 +166,15 @@ object Completion {
167166
val grouped = elems.groupBy(f)
168167
keys.map(key => key -> grouped(key))
169168

170-
val grouped = ctx.outersIterator.toList.reverse.orderedGroupBy(_.owner).filter(_._1.exists)
171-
val imported = grouped.map { (owner, contexts) =>
169+
val contextsByOwner = ctx.outersIterator.toList
170+
.reverse.orderedGroupBy(_.owner)
171+
.filter(_._1.exists)
172+
val imported = contextsByOwner.map { (_, contexts) =>
172173
contexts.collect { case context if context.isImportContext =>
173174
importedCompletions(using context)
174175
}.foldLeft(CompletionScope.empty)(_.mergeDiscardingAmbiguities(_))
175176
}
176-
val members = grouped.map { (owner, _) =>
177+
val members = contextsByOwner.map { (owner, _) =>
177178
if owner.isClass then
178179
CompletionScope.from(accessibleMembers(owner.thisType))
179180
else CompletionScope.empty
@@ -209,7 +210,10 @@ object Completion {
209210
val types = imp.site.member(name.toTypeName).alternatives.map(denot => nameInScope.toTypeName -> denot)
210211
CompletionScope.fromNamed(terms ++ types)
211212

212-
val givenImports = CompletionScope.fromNamed(imp.importedImplicits.map(x => (x.implicitName, x.underlyingRef.denot.asSingleDenotation)))
213+
val namedImportedImplicits = imp.importedImplicits.map { ref =>
214+
(ref.implicitName, ref.underlyingRef.denot.asSingleDenotation)
215+
}
216+
val givenImports = CompletionScope.fromNamed(namedImportedImplicits)
213217

214218
val wildcardMembers =
215219
if imp.selectors.exists(_.imported.name == nme.WILDCARD) then
@@ -243,8 +247,8 @@ object Completion {
243247
case _: MethodOrPoly => tpe
244248
case _ => ExprType(tpe)
245249

246-
def tryApplyingReceiver(termRef: TermRef): Option[SingleDenotation] =
247-
ctx.typer.tryApplyingReceiver(termRef, qual)
250+
def tryApplyingReceiverToExtension(termRef: TermRef): Option[SingleDenotation] =
251+
ctx.typer.tryApplyingExtensionMethod(termRef, qual)
248252
.map { tree =>
249253
val tpe = asDefLikeType(tree.tpe.dealias)
250254
termRef.denot.asSingleDenotation.mapInfo(_ => tpe)
@@ -259,15 +263,16 @@ object Completion {
259263
case name: TermName if name.startsWith(matchingNamePrefix) => Some((denot, name))
260264
case _ => None
261265

262-
types.flatMap{ tpe =>
266+
types.flatMap { tpe =>
263267
tpe.membersBasedOnFlags(required = ExtensionMethod, excluded = EmptyFlags)
264268
.collect { case DenotWithMatchingName(denot, name) => TermRef(tpe, denot.symbol) -> name }
265269
}
266270

267271
// There are four possible ways for an extension method to be applicable
268272

269273
// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
270-
val extMethodsInScope = scopeCompletions.nameToDenots.toList.flatMap {
274+
val termCompleter = new Completer(Mode.Term, prefix, pos)
275+
val extMethodsInScope = termCompleter.scopeCompletions.nameToDenots.toList.flatMap {
271276
case (name, denots) => denots.collect { case d: SymDenotation => (d.termRef, name.asTermName) }
272277
}
273278

@@ -287,8 +292,8 @@ object Completion {
287292
val extMethodsWithAppliedReceiver = availableExtMethods.flatMap {
288293
case (termRef, termName) =>
289294
if termRef.symbol.is(ExtensionMethod) && !qual.tpe.isBottomType then
290-
val applied = tryApplyingReceiver(termRef)
291-
applied.map{ denot =>
295+
val applied = tryApplyingReceiverToExtension(termRef)
296+
applied.map { denot =>
292297
val sym = denot.symbol.asTerm.copy(name = termName)
293298
denot.derivedSingleDenotation(sym, denot.info)
294299
}
@@ -326,23 +331,17 @@ object Completion {
326331
/** @param site The type to inspect.
327332
* @return The members of `site` that are accessible and pass the include filter.
328333
*/
329-
private def accessibleMembers(site: Type)(using Context): Seq[SingleDenotation] = site match {
330-
case site: NamedType if site.symbol.is(Package) =>
331-
extension (tpe: Type)
332-
def accessibleMembers = tpe.allMembers.toList.filter(denot => denot.symbol.isAccessibleFrom(site, superAccess = false))
333-
334-
val packageDecls = site.accessibleMembers
335-
val packageObjectsDecls = packageDecls.filter(_.symbol.isPackageObject).flatMap(_.symbol.thisType.accessibleMembers)
336-
337-
packageDecls ++ packageObjectsDecls
338-
case _ =>
339-
def appendMemberSyms(name: Name, buf: mutable.Buffer[SingleDenotation]): Unit =
340-
try buf ++= site.member(name).alternatives
341-
catch { case ex: TypeError => }
342-
site.memberDenots(completionsFilter, appendMemberSyms).collect {
343-
case mbr if include(mbr.symbol, mbr.symbol.name)
344-
&& mbr.symbol.isAccessibleFrom(site, superAccess = true) => mbr
345-
}
334+
private def accessibleMembers(site: Type)(using Context): Seq[SingleDenotation] = {
335+
def appendMemberSyms(name: Name, buf: mutable.Buffer[SingleDenotation]): Unit =
336+
try
337+
buf ++= site.member(name).alternatives
338+
catch
339+
case ex: TypeError =>
340+
341+
site.memberDenots(completionsFilter, appendMemberSyms).collect {
342+
case mbr if include(mbr.symbol, mbr.symbol.name)
343+
&& mbr.symbol.isAccessibleFrom(site) => mbr
344+
}
346345
}
347346

348347
/**
@@ -367,27 +366,60 @@ object Completion {
367366
def isStable = true
368367
}
369368

370-
extension (scope: CompletionScope)
371-
private def withDenots(denotations: Seq[SingleDenotation], name: Name)(using Context): CompletionScope = {
372-
val denots = denotations.filter(denot => include(denot.symbol, name))
373-
val shortName = name.stripModuleClassSuffix
374-
375-
if denots.nonEmpty then
376-
CompletionScope(scope.nameToDenots + (shortName -> denots.toList))
377-
else
378-
scope
369+
object CompletionScope {
370+
def from(denotations: Seq[SingleDenotation])(using Context): CompletionScope = {
371+
val mappings = denotations
372+
.filter(den => include(den.symbol, den.name))
373+
.toList
374+
.groupBy(_.name)
375+
.map((name, denots) => name.stripModuleClassSuffix -> denots)
376+
CompletionScope(mappings)
379377
}
380378

381-
extension (scope: CompletionScope.type)
382-
private def from(denots: Seq[SingleDenotation])(using Context): CompletionScope = {
383-
val mappings = denots.filter(den => include(den.symbol, den.name)).toList.groupBy(_.name).map( (name, denots) => name.stripModuleClassSuffix -> denots)
379+
def fromNamed(namedDenotations: Seq[(Name, SingleDenotation)])(using Context): CompletionScope = {
380+
val mappings = namedDenotations
381+
.filter((name, denot) => include(denot.symbol, name))
382+
.toList
383+
.groupBy(_._1)
384+
.map((name, namedDenots) => name.stripModuleClassSuffix -> namedDenots.map(_._2))
384385
CompletionScope(mappings)
385386
}
386387

387-
private def fromNamed(namedDenots: Seq[(Name, SingleDenotation)])(using Context): CompletionScope = {
388-
val mappings = namedDenots.filter((name, den) => include(den.symbol, name)).toList.groupBy(_._1).map( (name, namedDens) => name.stripModuleClassSuffix -> namedDens.map(_._2))
388+
val empty = CompletionScope()
389+
}
390+
391+
/** A wrapper over a map from names to their denotations
392+
* taking care of filtering out not matching completions
393+
* and enabling easy resolving of conflicts between different denotations with the same name
394+
*/
395+
case class CompletionScope private(nameToDenots: Map[Name, List[SingleDenotation]] = Map.empty) {
396+
397+
// Merge two scopes using mappings from `that` instead of from `this` in case of name clashes
398+
def mergeShadowedBy(that: CompletionScope) = CompletionScope(this.nameToDenots ++ that.nameToDenots)
399+
400+
// Merge two scopes but discard mappings for names that appear in both scopes
401+
def mergeDiscardingAmbiguities(that: CompletionScope) =
402+
val mappings = (this.nameToDenots.toList ++ that.nameToDenots.toList)
403+
.groupBy(_._1)
404+
.collect {
405+
case (name, (_, denots) :: Nil) => name -> denots
406+
}
389407
CompletionScope(mappings)
408+
409+
/**
410+
* Return the list of symbols that should be included in completion results.
411+
*
412+
* If several symbols share the same name, the type symbols appear before term symbols inside
413+
* the same `Completion`.
414+
*/
415+
def getCompletions(using Context): List[Completion] = {
416+
nameToDenots.toList.groupBy(_._1.toTermName.show).map { (name, namedDenots) =>
417+
val typesFirst = namedDenots.flatMap(_._2).sortWith((s1, s2) => s1.isType && !s2.isType)
418+
val desc = description(typesFirst)
419+
Completion(name, desc, typesFirst.map(_.symbol))
420+
}.toList
390421
}
422+
}
391423
}
392424

393425
/**
@@ -411,35 +443,5 @@ object Completion {
411443
/** Both term and type symbols are allowed */
412444
val Import: Mode = new Mode(4) | Term | Type
413445
}
414-
415-
private object CompletionScope {
416-
val empty = CompletionScope()
417-
}
418-
419-
private case class CompletionScope(nameToDenots: Map[Name, List[SingleDenotation]] = Map.empty) {
420-
def mergeShadowedBy(that: CompletionScope) = CompletionScope(this.nameToDenots ++ that.nameToDenots)
421-
422-
def mergeDiscardingAmbiguities(that: CompletionScope) =
423-
val mappings = (this.nameToDenots.toList ++ that.nameToDenots.toList)
424-
.groupBy(_._1)
425-
.collect {
426-
case (name, (_, denots) :: Nil) => name -> denots
427-
}
428-
CompletionScope(mappings)
429-
430-
/**
431-
* Return the list of symbols that should be included in completion results.
432-
*
433-
* If several symbols share the same name, the type symbols appear before term symbols inside
434-
* the same `Completion`.
435-
*/
436-
def getCompletions(using Context): List[Completion] = {
437-
nameToDenots.toList.groupBy(_._1.toTermName.show).map { (name, namedDenots) =>
438-
val typesFirst = namedDenots.flatMap(_._2).sortWith((s1, s2) => s1.isType && !s2.isType)
439-
val desc = description(typesFirst)
440-
Completion(name, desc, typesFirst.map(_.symbol))
441-
}.toList
442-
}
443-
}
444446
}
445447

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import Constants.{Constant, IntTag, LongTag}
3838
import Denotations.SingleDenotation
3939
import annotation.{constructorOnly, threadUnsafe}
4040

41+
import scala.util.control.NonFatal
42+
4143
object Applications {
4244
import tpd._
4345

@@ -2140,11 +2142,11 @@ trait Applications extends Compatibility {
21402142
*
21412143
* return the tree representing methodRef partially applied to the receiver and all the implicit parameters preceding it (A, B, C)
21422144
* with the type parameters of the extension (T1, T2) inferred.
2143-
* A failure is returned if the implicit search fails for any of the leading implicit parameters or if the receiver has a wrong type
2145+
* None is returned if the implicit search fails for any of the leading implicit parameters or if the receiver has a wrong type
21442146
* (note that in general the type of the receiver might depend on the exact types of the found instances of the proceding implicits).
21452147
* No implicit search is tried for implicits following the receiver or for parameters of the def (D, E).
21462148
*/
2147-
private def applyWithoutPostreceiverImplicits(methodRef: TermRef, receiver: Tree)(using Context): scala.util.Try[Tree] =
2149+
def tryApplyingExtensionMethod(methodRef: TermRef, receiver: Tree)(using Context): Option[Tree] =
21482150
// Drop all parameters sections of an extension method following the receiver; the return type after truncation is not important
21492151
def truncateExtension(tp: Type)(using Context): Type = tp match
21502152
case poly: PolyType =>
@@ -2154,27 +2156,28 @@ trait Applications extends Compatibility {
21542156
case meth: MethodType =>
21552157
meth.newLikeThis(meth.paramNames, meth.paramInfos, defn.AnyType)
21562158

2159+
def replaceCallee(inTree: Tree, replacement: Tree)(using Context): Tree = inTree match
2160+
case Apply(fun, args) => Apply(replaceCallee(fun, replacement), args)
2161+
case TypeApply(fun, args) => TypeApply(replaceCallee(fun, replacement), args)
2162+
case _: Ident => replacement
2163+
21572164
val truncatedSym = methodRef.symbol.asTerm.copy(owner = defn.RootPackage, name = Names.termName(""), info = truncateExtension(methodRef.info))
21582165
val truncatedRef = ref(truncatedSym).withSpan(receiver.span)
21592166
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter))
2160-
scala.util.Try {
2161-
inContext(newCtx) {
2167+
2168+
try
2169+
val appliedTree = inContext(newCtx) {
21622170
ctx.enter(truncatedSym)
21632171
ctx.typer.extMethodApply(truncatedRef, receiver, WildcardType)
21642172
}
2165-
}.filter(tree => tree.tpe.exists && !tree.tpe.isError)
2166-
2167-
def tryApplyingReceiver(methodRef: TermRef, receiver: Tree)(using Context): Option[Tree] =
2168-
def replaceCallee(inTree: Tree, replacement: Tree)(using Context): Tree = inTree match
2169-
case Apply(fun, args) => Apply(replaceCallee(fun, replacement), args)
2170-
case TypeApply(fun, args) => TypeApply(replaceCallee(fun, replacement), args)
2171-
case _: Ident => replacement
2172-
2173-
applyWithoutPostreceiverImplicits(methodRef, receiver)
2174-
.toOption
2175-
.map(tree => replaceCallee(tree, ref(methodRef)))
2173+
if appliedTree.tpe.exists && !appliedTree.tpe.isError then
2174+
Some(replaceCallee(appliedTree, ref(methodRef)))
2175+
else
2176+
None
2177+
catch
2178+
case NonFatal(_) => None
21762179

21772180
def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean =
21782181
methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType &&
2179-
applyWithoutPostreceiverImplicits(methodRef, Typed(EmptyTree, TypeTree(receiverType))).isSuccess
2182+
tryApplyingExtensionMethod(methodRef, Typed(EmptyTree, TypeTree(receiverType))).nonEmpty
21802183
}

compiler/test/dotty/tools/repl/TabcompleteTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ class TabcompleteTests extends ReplTest {
107107
@Test def `null` = fromInitialState { implicit s =>
108108
val comp = tabComplete("null.")
109109
assertEquals(
110-
List("!=", "##", "==", "asInstanceOf", "clone", "eq", "equals", "finalize", "getClass", "hashCode",
110+
List("!=", "##", "==", "asInstanceOf", "eq", "equals", "getClass", "hashCode",
111111
"isInstanceOf", "ne", "notify", "notifyAll", "synchronized", "toString", "wait"),
112112
comp.distinct.sorted)
113113
}
114114

115115
@Test def anyRef = fromInitialState { implicit s =>
116116
val comp = tabComplete("(null: AnyRef).")
117117
assertEquals(
118-
List("!=", "##", "->", "==", "asInstanceOf", "clone", "ensuring", "eq", "equals", "finalize", "formatted",
118+
List("!=", "##", "->", "==", "asInstanceOf", "ensuring", "eq", "equals", "formatted",
119119
"getClass", "hashCode", "isInstanceOf", "ne", "nn", "notify", "notifyAll", "synchronized", "toString", "wait", ""),
120120
comp.distinct.sorted)
121121
}

0 commit comments

Comments
 (0)