Skip to content

Commit ab667c5

Browse files
Backport "presentation-compiler: Add synthetic decorations" to LTS (#20759)
Backports #18951 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 785fea7 + 7a74114 commit ab667c5

12 files changed

+902
-72
lines changed

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

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ abstract class PcCollector[T](
3636
val uri = params.uri().nn
3737
val filePath = Paths.get(uri).nn
3838
val sourceText = params.text().nn
39+
val text = sourceText.toCharArray().nn
3940
val source =
4041
SourceFile.virtual(filePath.toString(), sourceText)
4142
driver.run(uri, source)
@@ -70,45 +71,6 @@ abstract class PcCollector[T](
7071
parent: Option[Tree]
7172
)(tree: Tree| EndMarker, pos: SourcePosition, symbol: Option[Symbol]): T
7273

73-
/**
74-
* @return (adjusted position, should strip backticks)
75-
*/
76-
def adjust(
77-
pos1: SourcePosition,
78-
forRename: Boolean = false
79-
): (SourcePosition, Boolean) =
80-
if !pos1.span.isCorrect then (pos1, false)
81-
else
82-
val pos0 =
83-
val span = pos1.span
84-
if span.exists && span.point > span.end then
85-
pos1.withSpan(
86-
span
87-
.withStart(span.point)
88-
.withEnd(span.point + (span.end - span.start))
89-
)
90-
else pos1
91-
92-
val pos =
93-
if pos0.end > 0 && sourceText(pos0.end - 1) == ',' then
94-
pos0.withEnd(pos0.end - 1)
95-
else pos0
96-
val isBackticked =
97-
sourceText(pos.start) == '`' &&
98-
pos.end > 0 &&
99-
sourceText(pos.end - 1) == '`'
100-
// when the old name contains backticks, the position is incorrect
101-
val isOldNameBackticked = sourceText(pos.start) != '`' &&
102-
pos.start > 0 &&
103-
sourceText(pos.start - 1) == '`' &&
104-
sourceText(pos.end) == '`'
105-
if isBackticked && forRename then
106-
(pos.withStart(pos.start + 1).withEnd(pos.end - 1), true)
107-
else if isOldNameBackticked then
108-
(pos.withStart(pos.start - 1).withEnd(pos.end + 1), false)
109-
else (pos, false)
110-
end adjust
111-
11274
def symbolAlternatives(sym: Symbol) =
11375
def member(parent: Symbol) = parent.info.member(sym.name).symbol
11476
def primaryConstructorTypeParam(owner: Symbol) =
@@ -447,7 +409,7 @@ abstract class PcCollector[T](
447409
*/
448410
case sel: Select
449411
if sel.span.isCorrect && filter(sel) &&
450-
!isForComprehensionMethod(sel) =>
412+
!sel.isForComprehensionMethod =>
451413
occurrences + collect(
452414
sel,
453415
pos.withSpan(selectNameSpan(sel))
@@ -602,17 +564,6 @@ abstract class PcCollector[T](
602564
Span(span.start, span.start + realName.length, point)
603565
else Span(point, span.end, point)
604566
else span
605-
606-
private val forCompMethods =
607-
Set(nme.map, nme.flatMap, nme.withFilter, nme.foreach)
608-
609-
// We don't want to collect synthethic `map`, `withFilter`, `foreach` and `flatMap` in for-comprenhensions
610-
private def isForComprehensionMethod(sel: Select): Boolean =
611-
val syntheticName = sel.name match
612-
case name: TermName => forCompMethods(name)
613-
case _ => false
614-
val wrongSpan = sel.qualifier.span.contains(sel.nameSpan)
615-
syntheticName && wrongSpan
616567
end PcCollector
617568

618569
object PcCollector:

presentation-compiler/src/main/dotty/tools/pc/PcDocumentHighlightProvider.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ final class PcDocumentHighlightProvider(
2323
toAdjust: SourcePosition,
2424
sym: Option[Symbol]
2525
): DocumentHighlight =
26-
val (pos, _) = adjust(toAdjust)
26+
val (pos, _) = toAdjust.adjust(text)
2727
tree match
2828
case _: NamedDefTree =>
2929
DocumentHighlight(pos.toLsp, DocumentHighlightKind.Write)

presentation-compiler/src/main/dotty/tools/pc/PcInlineValueProviderImpl.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ final class PcInlineValueProviderImpl(
2727
) extends PcCollector[Option[Occurence]](driver, params)
2828
with InlineValueProvider:
2929

30-
val text = params.text().nn.toCharArray().nn
31-
3230
val position: l.Position = pos.toLsp.getStart().nn
3331

3432
override def collect(parent: Option[Tree])(
@@ -38,7 +36,7 @@ final class PcInlineValueProviderImpl(
3836
): Option[Occurence] =
3937
tree match
4038
case tree: Tree =>
41-
val (adjustedPos, _) = adjust(pos)
39+
val (adjustedPos, _) = pos.adjust(text)
4240
Some(Occurence(tree, parent, adjustedPos))
4341
case _ => None
4442

presentation-compiler/src/main/dotty/tools/pc/PcRenameProvider.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class PcRenameProvider(
3535
def collect(
3636
parent: Option[Tree]
3737
)(tree: Tree | EndMarker, toAdjust: SourcePosition, sym: Option[Symbol]): l.TextEdit =
38-
val (pos, stripBackticks) = adjust(toAdjust, forRename = true)
38+
val (pos, stripBackticks) = toAdjust.adjust(text, forRename = true)
3939
l.TextEdit(
4040
pos.toLsp,
4141
if stripBackticks then newName.stripBackticks else newName

presentation-compiler/src/main/dotty/tools/pc/PcSemanticTokensProvider.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ final class PcSemanticTokensProvider(
7474
Some(
7575
makeNode(
7676
sym = sym,
77-
pos = adjust(pos)._1,
77+
pos = pos.adjust(text)._1,
7878
isDefinition = isDefinition(tree),
7979
isDeclaration = isDeclaration(tree)
8080
)
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
package dotty.tools.pc
2+
3+
4+
import java.nio.file.Paths
5+
6+
import scala.meta.internal.metals.ReportContext
7+
import dotty.tools.pc.utils.MtagsEnrichments.*
8+
import dotty.tools.pc.printer.ShortenedTypePrinter
9+
import scala.meta.pc.SymbolSearch
10+
import scala.meta.pc.SyntheticDecoration
11+
import scala.meta.pc.SyntheticDecorationsParams
12+
import scala.meta.internal.pc.DecorationKind
13+
import scala.meta.internal.pc.Decoration
14+
15+
16+
import dotty.tools.dotc.ast.tpd
17+
import dotty.tools.dotc.ast.tpd.*
18+
import dotty.tools.dotc.core.Contexts.Context
19+
import dotty.tools.dotc.core.Flags
20+
import dotty.tools.dotc.core.StdNames.*
21+
import dotty.tools.dotc.core.Types.*
22+
import dotty.tools.dotc.interactive.Interactive
23+
import dotty.tools.dotc.interactive.InteractiveDriver
24+
import dotty.tools.dotc.util.SourceFile
25+
import dotty.tools.dotc.util.SourcePosition
26+
import dotty.tools.dotc.util.Spans.Span
27+
import dotty.tools.pc.IndexedContext
28+
29+
final class PcSyntheticDecorationsProvider(
30+
driver: InteractiveDriver,
31+
params: SyntheticDecorationsParams,
32+
symbolSearch: SymbolSearch,
33+
)(using ReportContext):
34+
35+
val uri = params.uri().nn
36+
val filePath = Paths.get(uri).nn
37+
val sourceText = params.text().nn
38+
val text = sourceText.toCharArray().nn
39+
val source =
40+
SourceFile.virtual(filePath.toString, sourceText)
41+
driver.run(uri, source)
42+
given ctx: Context = driver.currentCtx
43+
val unit = driver.currentCtx.run.nn.units.head
44+
45+
def tpdTree = unit.tpdTree
46+
47+
def provide(): List[SyntheticDecoration] =
48+
val deepFolder = DeepFolder[Synthetics](collectDecorations)
49+
deepFolder(Synthetics.empty, tpdTree).decorations
50+
51+
def collectDecorations(
52+
decorations: Synthetics,
53+
tree: Tree,
54+
): Synthetics =
55+
tree match
56+
case ImplicitConversion(name, range) if params.implicitConversions() =>
57+
val adjusted = range.adjust(text)._1
58+
decorations
59+
.add(
60+
Decoration(
61+
adjusted.startPos.toLsp,
62+
name + "(",
63+
DecorationKind.ImplicitConversion,
64+
)
65+
)
66+
.add(
67+
Decoration(
68+
adjusted.endPos.toLsp,
69+
")",
70+
DecorationKind.ImplicitConversion,
71+
)
72+
)
73+
case ImplicitParameters(names, pos, allImplicit)
74+
if params.implicitParameters() =>
75+
val label =
76+
if allImplicit then names.mkString("(", ", ", ")")
77+
else names.mkString(", ", ", ", "")
78+
decorations.add(
79+
Decoration(
80+
pos.adjust(text)._1.toLsp,
81+
label,
82+
DecorationKind.ImplicitParameter,
83+
)
84+
)
85+
case TypeParameters(tpes, pos, sel)
86+
if params.typeParameters() && !syntheticTupleApply(sel) =>
87+
val label = tpes.map(toLabel(_, pos)).mkString("[", ", ", "]")
88+
decorations.add(
89+
Decoration(
90+
pos.adjust(text)._1.endPos.toLsp,
91+
label,
92+
DecorationKind.TypeParameter,
93+
)
94+
)
95+
case InferredType(tpe, pos, defTree) if params.inferredTypes() =>
96+
val adjustedPos = pos.adjust(text)._1.endPos
97+
if decorations.containsDef(adjustedPos.start) then decorations
98+
else
99+
decorations.add(
100+
Decoration(
101+
adjustedPos.toLsp,
102+
": " + toLabel(tpe, pos),
103+
DecorationKind.InferredType,
104+
),
105+
adjustedPos.start,
106+
)
107+
case _ => decorations
108+
109+
private def toLabel(
110+
tpe: Type,
111+
pos: SourcePosition,
112+
): String =
113+
val tpdPath =
114+
Interactive.pathTo(unit.tpdTree, pos.span)
115+
116+
val indexedCtx = IndexedContext(Interactive.contextOfPath(tpdPath))
117+
val printer = ShortenedTypePrinter(
118+
symbolSearch
119+
)(using indexedCtx)
120+
def optDealias(tpe: Type): Type =
121+
def isInScope(tpe: Type): Boolean =
122+
tpe match
123+
case tref: TypeRef =>
124+
indexedCtx.lookupSym(
125+
tref.currentSymbol
126+
) == IndexedContext.Result.InScope
127+
case AppliedType(tycon, args) =>
128+
isInScope(tycon) && args.forall(isInScope)
129+
case _ => true
130+
if isInScope(tpe)
131+
then tpe
132+
else tpe.metalsDealias(using indexedCtx.ctx)
133+
134+
val dealiased = optDealias(tpe)
135+
printer.tpe(dealiased)
136+
end toLabel
137+
138+
private val definitions = IndexedContext(ctx).ctx.definitions
139+
private def syntheticTupleApply(tree: Tree): Boolean =
140+
tree match
141+
case sel: Select =>
142+
if definitions.isTupleNType(sel.symbol.info.finalResultType) then
143+
sel match
144+
case Select(tupleClass: Ident, _)
145+
if !tupleClass.span.isZeroExtent &&
146+
tupleClass.span.exists &&
147+
tupleClass.name.startsWith("Tuple") =>
148+
val pos = tupleClass.sourcePos
149+
!sourceText.slice(pos.start, pos.end).mkString.startsWith("Tuple")
150+
case _ => true
151+
else false
152+
case _ => false
153+
end PcSyntheticDecorationsProvider
154+
155+
object ImplicitConversion:
156+
def unapply(tree: Tree)(using Context) =
157+
tree match
158+
case Apply(fun: Ident, args) if isSynthetic(fun) =>
159+
implicitConversion(fun, args)
160+
case Apply(Select(fun, name), args)
161+
if name == nme.apply && isSynthetic(fun) =>
162+
implicitConversion(fun, args)
163+
case _ => None
164+
private def isSynthetic(tree: Tree)(using Context) =
165+
tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit)
166+
167+
private def implicitConversion(fun: Tree, args: List[Tree])(using Context) =
168+
val lastArgPos =
169+
args.lastOption.map(_.sourcePos).getOrElse(fun.sourcePos)
170+
Some(
171+
fun.symbol.decodedName,
172+
lastArgPos.withStart(fun.sourcePos.start),
173+
)
174+
end ImplicitConversion
175+
176+
object ImplicitParameters:
177+
def unapply(tree: Tree)(using Context) =
178+
tree match
179+
case Apply(fun, args)
180+
if args.exists(isSyntheticArg) && !tree.sourcePos.span.isZeroExtent =>
181+
val (implicitArgs, providedArgs) = args.partition(isSyntheticArg)
182+
val allImplicit = providedArgs.isEmpty
183+
val pos = implicitArgs.head.sourcePos
184+
Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit)
185+
case Apply(ta @ TypeApply(fun, _), _)
186+
if fun.span.isSynthetic && isValueOf(fun) =>
187+
Some(
188+
List("new " + tpnme.valueOf.decoded.capitalize + "(...)"),
189+
fun.sourcePos,
190+
true,
191+
)
192+
case _ => None
193+
private def isValueOf(tree: Tree)(using Context) =
194+
val symbol = tree.symbol.maybeOwner
195+
symbol.name.decoded == tpnme.valueOf.decoded.capitalize
196+
private def isSyntheticArg(tree: Tree)(using Context) = tree match
197+
case tree: Ident =>
198+
tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit)
199+
case _ => false
200+
end ImplicitParameters
201+
202+
object TypeParameters:
203+
def unapply(tree: Tree)(using Context) =
204+
tree match
205+
case TypeApply(sel: Select, _) if sel.isForComprehensionMethod => None
206+
case TypeApply(fun, args) if inferredTypeArgs(args) =>
207+
val pos = fun match
208+
case sel: Select if sel.isInfix =>
209+
sel.sourcePos.withEnd(sel.nameSpan.end)
210+
case _ => fun.sourcePos
211+
val tpes = args.map(_.tpe.stripTypeVar.widen.finalResultType)
212+
Some((tpes, pos.endPos, fun))
213+
case _ => None
214+
private def inferredTypeArgs(args: List[Tree]): Boolean =
215+
args.forall {
216+
case tt: TypeTree if tt.span.exists && !tt.span.isZeroExtent => true
217+
case _ => false
218+
}
219+
end TypeParameters
220+
221+
object InferredType:
222+
def unapply(tree: Tree)(using Context) =
223+
tree match
224+
case vd @ ValDef(_, tpe, _)
225+
if isValidSpan(tpe.span, vd.nameSpan) &&
226+
!vd.symbol.is(Flags.Enum) =>
227+
if vd.symbol == vd.symbol.sourceSymbol then
228+
Some(tpe.tpe, tpe.sourcePos.withSpan(vd.nameSpan), vd)
229+
else None
230+
case vd @ DefDef(_, _, tpe, _)
231+
if isValidSpan(tpe.span, vd.nameSpan) &&
232+
tpe.span.start >= vd.nameSpan.end &&
233+
!vd.symbol.isConstructor &&
234+
!vd.symbol.is(Flags.Mutable) =>
235+
if vd.symbol == vd.symbol.sourceSymbol then
236+
Some(tpe.tpe, tpe.sourcePos, vd)
237+
else None
238+
case bd @ Bind(
239+
name,
240+
Ident(nme.WILDCARD),
241+
) =>
242+
Some(bd.symbol.info, bd.namePos, bd)
243+
case _ => None
244+
245+
private def isValidSpan(tpeSpan: Span, nameSpan: Span): Boolean =
246+
tpeSpan.isZeroExtent &&
247+
nameSpan.exists &&
248+
!nameSpan.isZeroExtent
249+
250+
end InferredType
251+
252+
case class Synthetics(
253+
decorations: List[Decoration],
254+
definitions: Set[Int],
255+
):
256+
def containsDef(offset: Int) = definitions(offset)
257+
def add(decoration: Decoration, offset: Int) =
258+
copy(
259+
decorations = decoration :: decorations,
260+
definitions = definitions + offset,
261+
)
262+
def add(decoration: Decoration) =
263+
copy(decorations = decoration :: decorations)
264+
265+
object Synthetics:
266+
def empty: Synthetics = Synthetics(Nil, Set.empty)

0 commit comments

Comments
 (0)