forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPcInlineValueProviderImpl.scala
207 lines (186 loc) · 6.9 KB
/
PcInlineValueProviderImpl.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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package dotty.tools.pc
import scala.meta.internal.pc.Definition
import scala.meta.internal.pc.InlineValueProvider
import scala.meta.internal.pc.InlineValueProvider.Errors
import scala.meta.internal.pc.RangeOffset
import scala.meta.internal.pc.Reference
import scala.meta.pc.OffsetParams
import dotty.tools.dotc.ast.NavigateAST
import dotty.tools.dotc.ast.tpd.*
import dotty.tools.dotc.ast.untpd
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.Symbol
import dotty.tools.dotc.interactive.Interactive
import dotty.tools.dotc.interactive.InteractiveDriver
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.pc.utils.InteractiveEnrichments.*
import dotty.tools.pc.IndexedContext.Result
import org.eclipse.lsp4j as l
final class PcInlineValueProviderImpl(
driver: InteractiveDriver,
val params: OffsetParams
) extends WithSymbolSearchCollector[Option[Occurence]](driver, params)
with InlineValueProvider:
val position: l.Position = pos.toLsp.getStart().nn
override def collect(parent: Option[Tree])(
tree: Tree | EndMarker,
pos: SourcePosition,
sym: Option[Symbol]
): Option[Occurence] =
tree match
case tree: Tree =>
val (adjustedPos, _) = pos.adjust(text)
Some(Occurence(tree, parent, adjustedPos))
case _ => None
override def defAndRefs(): Either[String, (Definition, List[Reference])] =
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
val allOccurences = result().flatten
for
definition <- allOccurences
.collectFirst { case Occurence(defn: ValDef, _, pos) =>
DefinitionTree(defn, pos)
}
.toRight(Errors.didNotFindDefinition)
path = Interactive.pathTo(unit.tpdTree, definition.tree.rhs.span)(using newctx)
indexedContext = IndexedContext(Interactive.contextOfPath(path)(using newctx))
symbols = symbolsUsedInDefn(definition.tree.rhs).filter(indexedContext.lookupSym(_) == Result.InScope)
references <- getReferencesToInline(definition, allOccurences, symbols)
yield
val (deleteDefinition, refsEdits) = references
val defPos = definition.tree.sourcePos
val defEdit = Definition(
defPos.toLsp,
adjustRhs(definition.tree.rhs.sourcePos),
RangeOffset(defPos.start, defPos.end),
definitionRequiresBrackets(definition.tree.rhs)(using newctx),
deleteDefinition
)
(defEdit, refsEdits)
end for
end defAndRefs
private def definitionRequiresBrackets(tree: Tree)(using Context): Boolean =
NavigateAST
.untypedPath(tree.span)
.headOption
.map {
case _: untpd.If => true
case _: untpd.Function => true
case _: untpd.Match => true
case _: untpd.ForYield => true
case _: untpd.InfixOp => true
case _: untpd.ParsedTry => true
case _: untpd.Try => true
case _: untpd.Block => true
case _: untpd.Typed => true
case _ => false
}
.getOrElse(false)
end definitionRequiresBrackets
private def referenceRequiresBrackets(tree: Tree)(using Context): Boolean =
NavigateAST.untypedPath(tree.span) match
case (_: untpd.InfixOp) :: _ => true
case _ =>
tree match
case _: Apply => StdNames.nme.raw.isUnary(tree.symbol.name)
case _: Select => true
case _: Ident => true
case _ => false
end referenceRequiresBrackets
private def adjustRhs(pos: SourcePosition) =
def extend(point: Int, acceptedChar: Char, step: Int): Int =
val newPoint = point + step
if newPoint > 0 && newPoint < text.length && text(
newPoint
) == acceptedChar
then extend(newPoint, acceptedChar, step)
else point
val adjustedStart = extend(pos.start, '(', -1)
val adjustedEnd = extend(pos.end - 1, ')', 1) + 1
text.slice(adjustedStart, adjustedEnd).mkString
private def symbolsUsedInDefn(rhs: Tree): Set[Symbol] =
def collectNames(
symbols: Set[Symbol],
tree: Tree
): Set[Symbol] =
tree match
case id: (Ident | Select)
if !id.symbol.is(Synthetic) && !id.symbol.is(Implicit) =>
symbols + tree.symbol
case _ => symbols
val traverser = new DeepFolder[Set[Symbol]](collectNames)
traverser(Set(), rhs)
end symbolsUsedInDefn
private def getReferencesToInline(
definition: DefinitionTree,
allOccurences: List[Occurence],
symbols: Set[Symbol]
): Either[String, (Boolean, List[Reference])] =
val defIsLocal = definition.tree.symbol.ownersIterator
.drop(1)
.exists(e => e.isTerm)
def allreferences = allOccurences.filterNot(_.isDefn)
def inlineAll() =
makeRefsEdits(allreferences, symbols).map((true, _))
if definition.tree.sourcePos.toLsp.encloses(position)
then if defIsLocal then inlineAll() else Left(Errors.notLocal)
else
allreferences match
case ref :: Nil if defIsLocal => inlineAll()
case list =>
for
ref <- list
.find(_.pos.toLsp.encloses(position))
.toRight(Errors.didNotFindReference)
refEdits <- makeRefsEdits(List(ref), symbols)
yield (false, refEdits)
end if
end getReferencesToInline
private def makeRefsEdits(
refs: List[Occurence],
symbols: Set[Symbol]
): Either[String, List[Reference]] =
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
def buildRef(occurrence: Occurence): Either[String, Reference] =
val path =
Interactive.pathTo(unit.tpdTree, occurrence.pos.span)(using newctx)
val indexedContext = IndexedContext(
Interactive.contextOfPath(path)(using newctx)
)
import indexedContext.ctx
val conflictingSymbols = symbols
.withFilter {
indexedContext.lookupSym(_) match
case IndexedContext.Result.Conflict => true
case _ => false
}
.map(_.fullNameBackticked)
if conflictingSymbols.isEmpty then
Right(
Reference(
occurrence.pos.toLsp,
occurrence.parent.map(p =>
RangeOffset(p.sourcePos.start, p.sourcePos.end)
),
occurrence.parent
.map(p => referenceRequiresBrackets(p)(using newctx))
.getOrElse(false)
)
)
else Left(Errors.variablesAreShadowed(conflictingSymbols.mkString(", ")))
end buildRef
refs.foldLeft((Right(List())): Either[String, List[Reference]])((acc, r) =>
for
collectedEdits <- acc
currentEdit <- buildRef(r)
yield currentEdit :: collectedEdits
)
end makeRefsEdits
end PcInlineValueProviderImpl
case class Occurence(tree: Tree, parent: Option[Tree], pos: SourcePosition):
def isDefn =
tree match
case _: ValDef => true
case _ => false
case class DefinitionTree(tree: ValDef, pos: SourcePosition)