Skip to content

Commit

Permalink
improvement: Replace synthetic decorations with inlay hints
Browse files Browse the repository at this point in the history
Backports changes from Metals: scalameta/metals#5983
  • Loading branch information
jkciesluk committed Feb 22, 2024
1 parent e54be6e commit be8abfa
Show file tree
Hide file tree
Showing 10 changed files with 932 additions and 692 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import java.nio.file.Paths
import scala.meta.internal.metals.ReportContext
import dotty.tools.pc.utils.MtagsEnrichments.*
import dotty.tools.pc.printer.ShortenedTypePrinter
import scala.meta.internal.pc.InlayHints
import scala.meta.internal.pc.LabelPart
import scala.meta.internal.pc.LabelPart.*
import scala.meta.pc.InlayHintsParams
import scala.meta.pc.SymbolSearch
import scala.meta.pc.SyntheticDecoration
import scala.meta.pc.SyntheticDecorationsParams
import scala.meta.internal.pc.DecorationKind
import scala.meta.internal.pc.Decoration


import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.tpd.*
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.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.interactive.Interactive
import dotty.tools.dotc.interactive.InteractiveDriver
Expand All @@ -26,9 +26,13 @@ import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.util.Spans.Span
import dotty.tools.pc.IndexedContext

final class PcSyntheticDecorationsProvider(
import org.eclipse.lsp4j.InlayHint
import org.eclipse.lsp4j.InlayHintKind
import org.eclipse.{lsp4j as l}

class PcInlayHintsProvider(
driver: InteractiveDriver,
params: SyntheticDecorationsParams,
params: InlayHintsParams,
symbolSearch: SymbolSearch,
)(using ReportContext):

Expand All @@ -43,75 +47,81 @@ final class PcSyntheticDecorationsProvider(
given InferredType.Text = InferredType.Text(text)
given ctx: Context = driver.currentCtx
val unit = driver.currentCtx.run.nn.units.head
val pos = driver.sourcePosition(params)

def tpdTree = unit.tpdTree
def provide(): List[InlayHint] =
val deepFolder = DeepFolder[InlayHints](collectDecorations)
Interactive
.pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx)
.headOption
.getOrElse(unit.tpdTree)
.enclosedChildren(pos.span)
.flatMap(tpdTree => deepFolder(InlayHints.empty, tpdTree).result())

def provide(): List[SyntheticDecoration] =
val deepFolder = DeepFolder[Synthetics](collectDecorations)
deepFolder(Synthetics.empty, tpdTree).result()
private def adjustPos(pos: SourcePosition): SourcePosition =
pos.adjust(text)._1

def collectDecorations(
decorations: Synthetics,
inlayHints: InlayHints,
tree: Tree,
): Synthetics =
): InlayHints =
tree match
case ImplicitConversion(name, range) if params.implicitConversions() =>
val adjusted = range.adjust(text)._1
decorations
case ImplicitConversion(symbol, range) if params.implicitConversions() =>
val adjusted = adjustPos(range)
inlayHints
.add(
Decoration(
adjusted.startPos.toLsp,
name + "(",
DecorationKind.ImplicitConversion,
)
adjusted.startPos.toLsp,
labelPart(symbol, symbol.decodedName) :: LabelPart("(") :: Nil,
InlayHintKind.Parameter,
)
.add(
Decoration(
adjusted.endPos.toLsp,
")",
DecorationKind.ImplicitConversion,
)
adjusted.endPos.toLsp,
LabelPart(")") :: Nil,
InlayHintKind.Parameter,
)
case ImplicitParameters(names, pos, allImplicit)
case ImplicitParameters(symbols, pos, allImplicit)
if params.implicitParameters() =>
val labelParts = symbols.map(s => List(labelPart(s, s.decodedName)))
val label =
if allImplicit then names.mkString("(", ", ", ")")
else names.mkString(", ", ", ", "")
decorations.add(
Decoration(
pos.adjust(text)._1.toLsp,
label,
DecorationKind.ImplicitParameter,
)
if allImplicit then labelParts.separated("(", ", ", ")")
else labelParts.separated(", ")
inlayHints.add(
adjustPos(pos).toLsp,
label,
InlayHintKind.Parameter,
)
case ValueOf(label, pos) if params.implicitParameters() =>
inlayHints.add(
adjustPos(pos).toLsp,
LabelPart("(") :: LabelPart(label) :: List(LabelPart(")")),
InlayHintKind.Parameter,
)
case TypeParameters(tpes, pos, sel)
if params.typeParameters() && !syntheticTupleApply(sel) =>
val label = tpes.map(toLabel(_, pos)).mkString("[", ", ", "]")
decorations.add(
Decoration(
pos.adjust(text)._1.endPos.toLsp,
label,
DecorationKind.TypeParameter,
)
val label = tpes.map(toLabelParts(_, pos)).separated("[", ", ", "]")
inlayHints.add(
adjustPos(pos).endPos.toLsp,
label,
InlayHintKind.Type,
)
case InferredType(tpe, pos, defTree) if params.inferredTypes() =>
val adjustedPos = pos.adjust(text)._1.endPos
if decorations.containsDef(adjustedPos.start) then decorations
case InferredType(tpe, pos, defTree)
if params.inferredTypes() && !isErrorTpe(tpe) =>
val adjustedPos = adjustPos(pos).endPos
if inlayHints.containsDef(adjustedPos.start) then inlayHints
else
decorations.add(
Decoration(
inlayHints
.add(
adjustedPos.toLsp,
": " + toLabel(tpe, pos),
DecorationKind.InferredType,
),
adjustedPos.start,
)
case _ => decorations
LabelPart(": ") :: toLabelParts(tpe, pos),
InlayHintKind.Type,
)
.addDefinition(adjustedPos.start)
case _ => inlayHints

private def toLabel(
private def toLabelParts(
tpe: Type,
pos: SourcePosition,
): String =
): List[LabelPart] =
val tpdPath =
Interactive.pathTo(unit.tpdTree, pos.span)

Expand All @@ -129,13 +139,15 @@ final class PcSyntheticDecorationsProvider(
case AppliedType(tycon, args) =>
isInScope(tycon) && args.forall(isInScope)
case _ => true
if isInScope(tpe)
then tpe
if isInScope(tpe) then tpe
else tpe.metalsDealias(using indexedCtx.ctx)

val dealiased = optDealias(tpe)
printer.tpe(dealiased)
end toLabel
val tpeStr = printer.tpe(dealiased)
val usedRenames = printer.getUsedRenames
val parts = partsFromType(dealiased, usedRenames)
InlayHints.makeLabelParts(parts, tpeStr)
end toLabelParts

private val definitions = IndexedContext(ctx).ctx.definitions
private def syntheticTupleApply(tree: Tree): Boolean =
Expand All @@ -152,7 +164,32 @@ final class PcSyntheticDecorationsProvider(
case _ => true
else false
case _ => false
end PcSyntheticDecorationsProvider

private def labelPart(symbol: Symbol, label: String) =
if symbol.source == pos.source then
LabelPart(
label,
pos = Some(symbol.sourcePos.toLsp.getStart().nn),
)
else
LabelPart(
label,
symbol = SemanticdbSymbols.symbolName(symbol),
)

private def partsFromType(
tpe: Type,
usedRenames: Map[Symbol, String],
): List[LabelPart] =
NamedPartsAccumulator(_ => true)(Nil, tpe)
.filter(_.symbol != NoSymbol)
.map { t =>
val label = usedRenames.get(t.symbol).getOrElse(t.symbol.decodedName)
labelPart(t.symbol, label)
}

private def isErrorTpe(tpe: Type): Boolean = tpe.isError
end PcInlayHintsProvider

object ImplicitConversion:
def unapply(tree: Tree)(using Context) =
Expand All @@ -170,7 +207,7 @@ object ImplicitConversion:
val lastArgPos =
args.lastOption.map(_.sourcePos).getOrElse(fun.sourcePos)
Some(
fun.symbol.decodedName,
fun.symbol,
lastArgPos.withStart(fun.sourcePos.start),
)
end ImplicitConversion
Expand All @@ -183,23 +220,29 @@ object ImplicitParameters:
val (implicitArgs, providedArgs) = args.partition(isSyntheticArg)
val allImplicit = providedArgs.isEmpty
val pos = implicitArgs.head.sourcePos
Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit)
Some(implicitArgs.map(_.symbol), pos, allImplicit)
case _ => None

private def isSyntheticArg(tree: Tree)(using Context) = tree match
case tree: Ident =>
tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit)
case _ => false
end ImplicitParameters

object ValueOf:
def unapply(tree: Tree)(using Context) =
tree match
case Apply(ta @ TypeApply(fun, _), _)
if fun.span.isSynthetic && isValueOf(fun) =>
Some(
List("new " + tpnme.valueOf.decoded.capitalize + "(...)"),
"new " + tpnme.valueOf.decoded.capitalize + "(...)",
fun.sourcePos,
true,
)
case _ => None
private def isValueOf(tree: Tree)(using Context) =
val symbol = tree.symbol.maybeOwner
symbol.name.decoded == tpnme.valueOf.decoded.capitalize
private def isSyntheticArg(tree: Tree)(using Context) = tree match
case tree: Ident =>
tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit)
case _ => false
end ImplicitParameters
end ValueOf

object TypeParameters:
def unapply(tree: Tree)(using Context) =
Expand Down Expand Up @@ -259,35 +302,7 @@ object InferredType:
*/
def isValDefBind(text: Text, vd: ValDef)(using Context) =
val afterDef = text.drop(vd.nameSpan.end)
val index = afterDef.indexAfterSpacesAndComments
val index = indexAfterSpacesAndComments(afterDef)
index >= 0 && index < afterDef.size && afterDef(index) == '@'

end InferredType

case class Synthetics(
decorations: List[Decoration],
definitions: Set[Int],
):
def containsDef(offset: Int) = definitions(offset)
def add(decoration: Decoration, offset: Int) =
copy(
decorations = addDecoration(decoration),
definitions = definitions + offset,
)
def add(decoration: Decoration) =
copy (
decorations = addDecoration(decoration)
)

// If method has both type parameter and implicit parameter, we want the type parameter decoration to be displayed first,
// but it's added second. This method adds the decoration to the right position in the list.
private def addDecoration(decoration: Decoration): List[Decoration] =
val atSamePos =
decorations.takeWhile(_.range.getStart() == decoration.range.getStart())
(atSamePos :+ decoration) ++ decorations.drop(atSamePos.size)

def result(): List[Decoration] = decorations.reverse
end Synthetics

object Synthetics:
def empty: Synthetics = Synthetics(Nil, Set.empty)
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ case class ScalaPresentationCompiler(
new PcSemanticTokensProvider(driver, params).provide().asJava
}

override def syntheticDecorations(
params: SyntheticDecorationsParams
): ju.concurrent.CompletableFuture[ju.List[SyntheticDecoration]] =
override def inlayHints(
params: InlayHintsParams
): ju.concurrent.CompletableFuture[ju.List[l.InlayHint]] =
compilerAccess.withInterruptableCompiler(Some(params))(
new ju.ArrayList[SyntheticDecoration](),
new ju.ArrayList[l.InlayHint](),
params.token(),
) { access =>
val driver = access.compiler()
new PcSyntheticDecorationsProvider(driver, params, search)
new PcInlayHintsProvider(driver, params, search)
.provide()
.asJava
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class ShortenedTypePrinter(

private val foundRenames = collection.mutable.LinkedHashMap.empty[Symbol, String]

def getUsedRenames: Map[Symbol, String] = foundRenames.toMap

def getUsedRenamesInfo(using Context): List[String] =
foundRenames.map { (from, to) =>
s"type $to = ${from.showName}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,24 @@ object MtagsEnrichments extends CommonMtagsEnrichments:
.map(source.apply)
.contains('.')
case _ => false

def children(using Context): List[Tree] =
val collector = new TreeAccumulator[List[Tree]]:
def apply(x: List[Tree], tree: Tree)(using Context): List[Tree] =
tree :: x
collector
.foldOver(Nil, tree)
.reverse

/**
* Returns the children of the tree that overlap with the given span.
*/
def enclosedChildren(span: Span)(using Context): List[Tree] =
tree.children
.filter(tree =>
tree.sourcePos.exists && tree.span.start <= span.end && tree.span.end >= span.start
)
end enclosedChildren
end extension

extension (imp: Import)
Expand Down
Loading

0 comments on commit be8abfa

Please sign in to comment.