forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 17
/
Interactive.scala
184 lines (163 loc) · 6.94 KB
/
Interactive.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package dotty.tools
package dotc
package interactive
import scala.annotation.tailrec
import scala.collection._
import ast.{NavigateAST, Trees, tpd, untpd}
import core._, core.Decorators.{sourcePos => _, _}
import Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._
import util.Positions._, util.SourcePosition
import NameKinds.SimpleNameKind
/** High-level API to get information out of typed trees, designed to be used by IDEs.
*
* @see `InteractiveDriver` to get typed trees from code.
*/
object Interactive {
import ast.tpd._
/** Does this tree define a symbol ? */
def isDefinition(tree: Tree) =
tree.isInstanceOf[DefTree with NameTree]
/** The type of the closest enclosing tree with a type containing position `pos`. */
def enclosingType(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): Type = {
val path = pathTo(trees, pos)
if (path.isEmpty) NoType
else path.head.tpe
}
/** The source symbol of the closest enclosing tree with a symbol containing position `pos`.
*
* @see sourceSymbol
*/
def enclosingSourceSymbol(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): Symbol = {
pathTo(trees, pos).dropWhile(!_.symbol.exists).headOption match {
case Some(tree) =>
sourceSymbol(tree.symbol)
case None =>
NoSymbol
}
}
/** A symbol related to `sym` that is defined in source code.
*
* @see enclosingSourceSymbol
*/
@tailrec def sourceSymbol(sym: Symbol)(implicit ctx: Context): Symbol =
if (!sym.exists)
sym
else if (sym.is(ModuleVal))
sourceSymbol(sym.moduleClass) // The module val always has a zero-extent position
else if (sym.is(Synthetic)) {
val linked = sym.linkedClass
if (linked.exists && !linked.is(Synthetic))
linked
else
sourceSymbol(sym.owner)
}
else if (sym.isPrimaryConstructor)
sourceSymbol(sym.owner)
else sym
/** Possible completions at position `pos` */
def completions(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): List[Symbol] = {
val path = pathTo(trees, pos)
val boundary = enclosingDefinitionInPath(path).symbol
path.take(1).flatMap {
case Select(qual, _) =>
// When completing "`a.foo`, return the members of `a`
completions(qual.tpe, boundary)
case _ =>
// FIXME: Get all declarations available in the current scope, not just
// those from the enclosing class
boundary.enclosingClass match {
case csym: ClassSymbol =>
val classRef = csym.classInfo.typeRef
completions(classRef, boundary)
case _ =>
Nil
}
}
}
/** Possible completions of members of `prefix` which are accessible when called inside `boundary` */
def completions(prefix: Type, boundary: Symbol)(implicit ctx: Context): List[Symbol] = {
val boundaryCtx = ctx.withOwner(boundary)
prefix.memberDenots(completionsFilter, (name, buf) =>
buf ++= prefix.member(name).altsWith(_.symbol.isAccessibleFrom(prefix)(boundaryCtx))
).map(_.symbol).toList
}
/** Filter for names that should appear when looking for completions. */
private[this] object completionsFilter extends NameFilter {
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
!name.isConstructorName && name.is(SimpleNameKind)
}
/** Find named trees with a non-empty position whose symbol match `sym` in `trees`.
*
* Note that nothing will be found for symbols not defined in source code,
* use `sourceSymbol` to get a symbol related to `sym` that is defined in
* source code.
*
* @param includeReferences If true, include references and not just definitions
* @param includeOverriden If true, include trees whose symbol is overriden by `sym`
*/
def namedTrees(trees: List[SourceTree], includeReferences: Boolean, includeOverriden: Boolean, sym: Symbol)
(implicit ctx: Context): List[SourceTree] =
if (!sym.exists)
Nil
else
namedTrees(trees, includeReferences, matchSymbol(_, sym, includeOverriden))
/** Find named trees with a non-empty position whose name contains `nameSubstring` in `trees`.
*
* @param includeReferences If true, include references and not just definitions
*/
def namedTrees(trees: List[SourceTree], includeReferences: Boolean, nameSubstring: String)
(implicit ctx: Context): List[SourceTree] =
namedTrees(trees, includeReferences, _.show.toString.contains(nameSubstring))
/** Find named trees with a non-empty position satisfying `treePredicate` in `trees`.
*
* @param includeReferences If true, include references and not just definitions
*/
def namedTrees(trees: List[SourceTree], includeReferences: Boolean, treePredicate: NameTree => Boolean)
(implicit ctx: Context): List[SourceTree] = {
val buf = new mutable.ListBuffer[SourceTree]
trees foreach { case SourceTree(topTree, source) =>
(new untpd.TreeTraverser {
override def traverse(tree: untpd.Tree)(implicit ctx: Context) = {
tree match {
case _: untpd.Inlined =>
// Skip inlined trees
case utree: untpd.NameTree if tree.hasType =>
val tree = utree.asInstanceOf[tpd.NameTree]
if (tree.symbol.exists
&& !tree.symbol.is(Synthetic)
&& tree.pos.exists
&& !tree.pos.isZeroExtent
&& (includeReferences || isDefinition(tree))
&& treePredicate(tree))
buf += SourceTree(tree, source)
case _ =>
}
traverseChildren(tree)
}
}).traverse(topTree)
}
buf.toList
}
/** Check if `tree` matches `sym`.
* This is the case if `sym` is the symbol of `tree` or, if `includeOverriden`
* is true, if `sym` is overriden by `tree`.
*/
def matchSymbol(tree: Tree, sym: Symbol, includeOverriden: Boolean)(implicit ctx: Context): Boolean =
(sym == tree.symbol) || (includeOverriden && tree.symbol.allOverriddenSymbols.contains(sym))
/** The reverse path to the node that closest encloses position `pos`,
* or `Nil` if no such path exists. If a non-empty path is returned it starts with
* the tree closest enclosing `pos` and ends with an element of `trees`.
*/
def pathTo(trees: List[SourceTree], pos: SourcePosition)(implicit ctx: Context): List[Tree] =
trees.find(_.pos.contains(pos)) match {
case Some(tree) =>
// FIXME: We shouldn't need a cast. Change NavigateAST.pathTo to return a List of Tree?
val path = NavigateAST.pathTo(pos.pos, tree.tree, skipZeroExtent = true).asInstanceOf[List[untpd.Tree]]
path.dropWhile(!_.hasType).asInstanceOf[List[tpd.Tree]]
case None =>
Nil
}
/** The first tree in the path that is a definition. */
def enclosingDefinitionInPath(path: List[Tree])(implicit ctx: Context): Tree =
path.find(_.isInstanceOf[DefTree]).getOrElse(EmptyTree)
}