Skip to content

Commit 297facb

Browse files
committed
Refactorings in Completion
- Move `CompletionInfo` out of `def completions` - Move `include`, `add`, etc. inside `CompletionInfo`
1 parent 43e3fbe commit 297facb

File tree

1 file changed

+96
-78
lines changed

1 file changed

+96
-78
lines changed

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

Lines changed: 96 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,6 @@ object Completion {
3333
}
3434

3535
private def computeCompletions(pos: SourcePosition, path: List[Tree])(implicit ctx: Context): (Int, List[Symbol]) = {
36-
val completions = Scopes.newScope.openForMutations
37-
38-
type Mode = Int
39-
object Mode {
40-
/** No symbol should be included */
41-
val None: Mode = 0
42-
43-
/** Term symbols are allowed */
44-
val Term: Mode = 1
45-
46-
/** Type symbols are allowed */
47-
val Type: Mode = 2
48-
49-
/** Both term and type symbols are allowed */
50-
val Import: Mode = Term | Type
51-
52-
/** Does `m0` include `m1`? */
53-
def is(m0: Mode, m1: Mode): Boolean = (m0 & m1) == m1
54-
}
55-
56-
/**
57-
* The information about the current completion.
58-
*
59-
* @param offset The offset where the completion result should be inserted.
60-
* @param prefix A prefix that potential completion results must match.
61-
* @param mode The completion mode.
62-
*/
63-
case class CompletionInfo(offset: Int, prefix: String, mode: Mode)
6436

6537
/**
6638
* Extract basic info about completion location and the kind of symbols to include.
@@ -106,70 +78,37 @@ object Completion {
10678
completionInfo(other, /* inImport = */ false)
10779
}
10880

109-
/** Include in completion sets only symbols that
110-
* 1. start with given name prefix, and
111-
* 2. do not contain '$' except in prefix where it is explicitly written by user, and
112-
* 3. are not a primary constructor,
113-
* 4. are the module class in case of packages,
114-
* 5. are mutable accessors, to exclude setters for `var`,
115-
* 6. have same term/type kind as name prefix given so far
116-
*
117-
* The reason for (2) is that we do not want to present compiler-synthesized identifiers
118-
* as completion results. However, if a user explicitly writes all '$' characters in an
119-
* identifier, we should complete the rest.
120-
*/
121-
def include(sym: Symbol) = {
122-
sym.name.startsWith(info.prefix) &&
123-
!sym.name.toString.drop(info.prefix.length).contains('$') &&
124-
!sym.isPrimaryConstructor &&
125-
(!sym.is(Package) || !sym.moduleClass.exists) &&
126-
!sym.is(allOf(Mutable, Accessor)) &&
127-
(
128-
(Mode.is(info.mode, Mode.Term) && sym.isTerm)
129-
|| (Mode.is(info.mode, Mode.Type) && sym.isType)
130-
)
131-
}
132-
133-
def enter(sym: Symbol) =
134-
if (include(sym)) completions.enter(sym)
135-
def add(sym: Symbol) =
136-
if (sym.exists && !completions.lookup(sym.name).exists) enter(sym)
137-
138-
def addMember(site: Type, name: Name) =
139-
if (!completions.lookup(name).exists)
140-
for (alt <- site.member(name).alternatives) enter(alt.symbol)
141-
14281
def accessibleMembers(site: Type, superAccess: Boolean = true): Seq[Symbol] = site match {
14382
case site: NamedType if site.symbol.is(Package) =>
144-
site.decls.toList.filter(include) // Don't look inside package members -- it's too expensive.
83+
site.decls.toList.filter(info.include) // Don't look inside package members -- it's too expensive.
14584
case _ =>
14685
def appendMemberSyms(name: Name, buf: mutable.Buffer[SingleDenotation]): Unit =
14786
try buf ++= site.member(name).alternatives
14887
catch { case ex: TypeError => }
14988
site.memberDenots(takeAllFilter, appendMemberSyms).collect {
150-
case mbr if include(mbr.symbol) => mbr.accessibleFrom(site, superAccess).symbol
89+
case mbr if info.include(mbr.symbol) => mbr.accessibleFrom(site, superAccess).symbol
15190
case _ => NoSymbol
15291
}.filter(_.exists)
15392
}
15493

15594
def addAccessibleMembers(site: Type, superAccess: Boolean = true): Unit =
156-
for (mbr <- accessibleMembers(site)) addMember(site, mbr.name)
95+
for (mbr <- accessibleMembers(site)) info.addMember(site, mbr.name)
15796

15897
def getImportCompletions(ictx: Context): Unit = {
15998
implicit val ctx = ictx
16099
val imp = ctx.importInfo
161100
if (imp != null) {
162101
def addImport(name: TermName) = {
163-
addMember(imp.site, name)
164-
addMember(imp.site, name.toTypeName)
102+
info.addMember(imp.site, name)
103+
info.addMember(imp.site, name.toTypeName)
165104
}
166105
// FIXME: We need to also take renamed items into account for completions,
167106
// That means we have to return list of a pairs (Name, Symbol) instead of a list
168107
// of symbols from `completions`.!=
169108
for (imported <- imp.originals if !imp.excluded.contains(imported)) addImport(imported)
170109
if (imp.isWildcardImport)
171110
for (mbr <- accessibleMembers(imp.site) if !imp.excluded.contains(mbr.name.toTermName))
172-
addMember(imp.site, mbr.name)
111+
info.addMember(imp.site, mbr.name)
173112
}
174113
}
175114

@@ -179,11 +118,11 @@ object Completion {
179118
if (ctx.owner.isClass) {
180119
addAccessibleMembers(ctx.owner.thisType)
181120
ctx.owner.asClass.classInfo.selfInfo match {
182-
case selfSym: Symbol => add(selfSym)
121+
case selfSym: Symbol => info.add(selfSym)
183122
case _ =>
184123
}
185124
}
186-
else if (ctx.scope != null) ctx.scope.foreach(add)
125+
else if (ctx.scope != null) ctx.scope.foreach(info.add)
187126

188127
getImportCompletions(ctx)
189128

@@ -205,7 +144,7 @@ object Completion {
205144

206145
def getMemberCompletions(qual: Tree): Unit = {
207146
addAccessibleMembers(qual.tpe)
208-
if (!Mode.is(info.mode, Mode.Import)) {
147+
if (!info.mode.is(Mode.Import)) {
209148
// Implicit conversions do not kick in when importing
210149
implicitConversionTargets(qual)(ctx.fresh.setExploreTyperState())
211150
.foreach(addAccessibleMembers(_))
@@ -219,24 +158,103 @@ object Completion {
219158
case _ => getScopeCompletions(ctx)
220159
}
221160

222-
val completionList =
223-
if (!Mode.is(info.mode, Mode.Import)) completions.toList
161+
val completionList = info.getCompletions
162+
163+
interactiv.println(i"completion with pos = $pos, prefix = $info.prefix, termOnly = $info.termOnly, typeOnly = $info.typeOnly = $completionList%, %")
164+
(info.offset, completionList)
165+
}
166+
167+
/** Filter for names that should appear when looking for completions. */
168+
private[this] object completionsFilter extends NameFilter {
169+
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
170+
!name.isConstructorName && name.toTermName.info.kind == SimpleNameKind
171+
}
172+
173+
/**
174+
* The information about the current completion.
175+
*
176+
* @param offset The offset where the completion result should be inserted.
177+
* @param prefix A prefix that potential completion results must match.
178+
* @param mode The completion mode.
179+
*/
180+
private case class CompletionInfo(offset: Int, prefix: String, mode: Mode) {
181+
182+
private[this] val completions = Scopes.newScope.openForMutations
183+
184+
/** Checks whether `sym` should be included, and adds it to the completions if so. */
185+
def enter(sym: Symbol)(implicit ctx: Context) =
186+
if (include(sym)) completions.enter(sym)
187+
188+
/** Checks whether `sym` should be included, and adds it to the completions if so. */
189+
def add(sym: Symbol)(implicit ctx: Context) =
190+
if (sym.exists && !completions.lookup(sym.name).exists) enter(sym)
191+
192+
/** Lookup members `name` from `site`, and try to add them to the completion list. */
193+
def addMember(site: Type, name: Name)(implicit ctx: Context) =
194+
if (!completions.lookup(name).exists)
195+
for (alt <- site.member(name).alternatives) enter(alt.symbol)
196+
197+
/** Include in completion sets only symbols that
198+
* 1. start with given name prefix, and
199+
* 2. do not contain '$' except in prefix where it is explicitly written by user, and
200+
* 3. are not a primary constructor,
201+
* 4. are the module class in case of packages,
202+
* 5. are mutable accessors, to exclude setters for `var`,
203+
* 6. have same term/type kind as name prefix given so far
204+
*
205+
* The reason for (2) is that we do not want to present compiler-synthesized identifiers
206+
* as completion results. However, if a user explicitly writes all '$' characters in an
207+
* identifier, we should complete the rest.
208+
*/
209+
def include(sym: Symbol)(implicit ctx: Context): Boolean =
210+
sym.name.startsWith(prefix) &&
211+
!sym.name.toString.drop(prefix.length).contains('$') &&
212+
!sym.isPrimaryConstructor &&
213+
(!sym.is(Package) || !sym.moduleClass.exists) &&
214+
!sym.is(allOf(Mutable, Accessor)) &&
215+
(
216+
(mode.is(Mode.Term) && sym.isTerm)
217+
|| (mode.is(Mode.Type) && sym.isType)
218+
)
219+
220+
/**
221+
* Return the list of symbols that shoudl be included in completion results.
222+
*
223+
* If the mode is `Import` and several symbols share the same name, the type symbols are
224+
* preferred over term symbols.
225+
*/
226+
def getCompletions(implicit ctx: Context): List[Symbol] = {
227+
if (!mode.is(Mode.Import)) completions.toList
224228
else {
225229
// In imports, show only the type symbols when there are multiple options with the same name
226230
completions.toList.groupBy(_.name.stripModuleClassSuffix.toSimpleName).mapValues {
227231
case sym :: Nil => sym :: Nil
228232
case syms => syms.filter(_.isType)
229233
}.values.flatten.toList
230234
}
235+
}
236+
}
231237

232-
interactiv.println(i"completion with pos = $pos, prefix = $info.prefix, termOnly = $info.termOnly, typeOnly = $info.typeOnly = $completionList%, %")
233-
(info.offset, completionList)
238+
/**
239+
* The completion mode: defines what kinds of symbols should be included in the completion
240+
* results.
241+
*/
242+
private class Mode(val bits: Int) extends AnyVal {
243+
def is(other: Mode): Boolean = (bits & other.bits) == other.bits
244+
def |(other: Mode): Mode = new Mode(bits | other.bits)
234245
}
246+
private object Mode {
247+
/** No symbol should be included */
248+
val None: Mode = new Mode(0)
235249

236-
/** Filter for names that should appear when looking for completions. */
237-
private[this] object completionsFilter extends NameFilter {
238-
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
239-
!name.isConstructorName && name.toTermName.info.kind == SimpleNameKind
250+
/** Term symbols are allowed */
251+
val Term: Mode = new Mode(1)
252+
253+
/** Type symbols are allowed */
254+
val Type: Mode = new Mode(2)
255+
256+
/** Both term and type symbols are allowed */
257+
val Import: Mode = Term | Type
240258
}
241259

242260
}

0 commit comments

Comments
 (0)